@dnax/ui 0.0.1

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.
Files changed (150) hide show
  1. package/README.md +15 -0
  2. package/components/QAccordion.vue +68 -0
  3. package/components/QAccordionContent.vue +62 -0
  4. package/components/QAccordionItem.vue +24 -0
  5. package/components/QAccordionTrigger.vue +51 -0
  6. package/components/QActionSheet.vue +159 -0
  7. package/components/QApp.vue +8 -0
  8. package/components/QAutocomplete.vue +316 -0
  9. package/components/QAvatar.vue +92 -0
  10. package/components/QBackHeader.vue +69 -0
  11. package/components/QBackTop.vue +125 -0
  12. package/components/QBadge.vue +55 -0
  13. package/components/QBottomSheet.vue +177 -0
  14. package/components/QBottomSheetFooter.vue +9 -0
  15. package/components/QBottomSheetHeader.vue +35 -0
  16. package/components/QBottomSheetProvider.vue +62 -0
  17. package/components/QBottomSheetTrigger.vue +18 -0
  18. package/components/QBtn.vue +144 -0
  19. package/components/QBtnGroup.vue +31 -0
  20. package/components/QBubble.vue +36 -0
  21. package/components/QBubbleContent.vue +28 -0
  22. package/components/QBubbleGroup.vue +25 -0
  23. package/components/QBubbleReactions.vue +40 -0
  24. package/components/QCard.vue +63 -0
  25. package/components/QCardActions.vue +27 -0
  26. package/components/QCardContent.vue +29 -0
  27. package/components/QCarousel.vue +115 -0
  28. package/components/QCarouselContent.vue +23 -0
  29. package/components/QCarouselItem.vue +20 -0
  30. package/components/QCarouselNav.vue +51 -0
  31. package/components/QCheckbox.vue +146 -0
  32. package/components/QChip.vue +91 -0
  33. package/components/QCircularProgress.vue +122 -0
  34. package/components/QCol.vue +52 -0
  35. package/components/QCollapse.vue +141 -0
  36. package/components/QConfigProvider.vue +164 -0
  37. package/components/QContainer.vue +201 -0
  38. package/components/QCountDown.vue +164 -0
  39. package/components/QCountryPicker.vue +593 -0
  40. package/components/QDataGrid.vue +491 -0
  41. package/components/QDatePicker.vue +250 -0
  42. package/components/QDialog.vue +163 -0
  43. package/components/QDialogFooter.vue +9 -0
  44. package/components/QDialogHeader.vue +41 -0
  45. package/components/QDialogProvider.vue +65 -0
  46. package/components/QDialogTrigger.vue +18 -0
  47. package/components/QFab.vue +80 -0
  48. package/components/QFabAction.vue +48 -0
  49. package/components/QFilePicker.vue +294 -0
  50. package/components/QFooter.vue +72 -0
  51. package/components/QGallery.vue +280 -0
  52. package/components/QGrid.vue +54 -0
  53. package/components/QHeader.vue +38 -0
  54. package/components/QIcon.vue +53 -0
  55. package/components/QImagePicker.vue +248 -0
  56. package/components/QImagePreview.vue +225 -0
  57. package/components/QImagePreviewProvider.vue +35 -0
  58. package/components/QImg.vue +142 -0
  59. package/components/QInfiniteScroll.vue +109 -0
  60. package/components/QInnerLoading.vue +67 -0
  61. package/components/QInput.vue +289 -0
  62. package/components/QInputOtp.vue +238 -0
  63. package/components/QInputPassword.vue +67 -0
  64. package/components/QInputTag.vue +281 -0
  65. package/components/QIntersection.vue +119 -0
  66. package/components/QItem.vue +60 -0
  67. package/components/QItemSection.vue +47 -0
  68. package/components/QLinearProgress.vue +94 -0
  69. package/components/QList.vue +39 -0
  70. package/components/QLoading.vue +77 -0
  71. package/components/QLoadingProvider.vue +41 -0
  72. package/components/QMarquee.vue +56 -0
  73. package/components/QMessageScroller.vue +10 -0
  74. package/components/QMessageScrollerButton.vue +48 -0
  75. package/components/QMessageScrollerContent.vue +31 -0
  76. package/components/QMessageScrollerItem.vue +49 -0
  77. package/components/QMessageScrollerProvider.vue +198 -0
  78. package/components/QMessageScrollerViewport.vue +109 -0
  79. package/components/QNavMenu.vue +75 -0
  80. package/components/QNavMenuContent.vue +10 -0
  81. package/components/QNavMenuItem.vue +60 -0
  82. package/components/QNavMenuTrigger.vue +96 -0
  83. package/components/QNotifyProvider.vue +33 -0
  84. package/components/QNotifyToast.vue +60 -0
  85. package/components/QPage.vue +56 -0
  86. package/components/QPagination.vue +195 -0
  87. package/components/QParallax.vue +124 -0
  88. package/components/QPullToRefresh.vue +138 -0
  89. package/components/QRadio.vue +99 -0
  90. package/components/QRating.vue +105 -0
  91. package/components/QReorder.vue +191 -0
  92. package/components/QRollingText.vue +225 -0
  93. package/components/QRow.vue +23 -0
  94. package/components/QSafeArea.vue +39 -0
  95. package/components/QScrollArea.vue +118 -0
  96. package/components/QSelect.vue +698 -0
  97. package/components/QSeparator.vue +39 -0
  98. package/components/QSidebar.vue +153 -0
  99. package/components/QSidebarContent.vue +14 -0
  100. package/components/QSidebarFooter.vue +14 -0
  101. package/components/QSidebarHeader.vue +14 -0
  102. package/components/QSidebarMenu.vue +9 -0
  103. package/components/QSidebarMenuButton.vue +59 -0
  104. package/components/QSidebarMenuItem.vue +9 -0
  105. package/components/QSidebarTrigger.vue +32 -0
  106. package/components/QSkeleton.vue +95 -0
  107. package/components/QSlider.vue +205 -0
  108. package/components/QSpace.vue +10 -0
  109. package/components/QSpinner.vue +172 -0
  110. package/components/QSplitter.vue +145 -0
  111. package/components/QSticky.vue +181 -0
  112. package/components/QSwipeCell.vue +217 -0
  113. package/components/QSwiper.vue +102 -0
  114. package/components/QSwiperSlide.vue +19 -0
  115. package/components/QSyntax.vue +171 -0
  116. package/components/QTab.vue +175 -0
  117. package/components/QTabPanel.vue +50 -0
  118. package/components/QTabPanels.vue +106 -0
  119. package/components/QTable.vue +575 -0
  120. package/components/QTabs.vue +188 -0
  121. package/components/QText.vue +316 -0
  122. package/components/QTextCaption.vue +122 -0
  123. package/components/QToolbar.vue +28 -0
  124. package/components/QTooltip.vue +313 -0
  125. package/components/QUploader.vue +473 -0
  126. package/components/QVideo.vue +289 -0
  127. package/components/QVirtualScroll.vue +190 -0
  128. package/components/internal/QDateCalendar.vue +153 -0
  129. package/index.ts +151 -0
  130. package/lib/breakpoints.ts +44 -0
  131. package/lib/colors.ts +24 -0
  132. package/lib/config.ts +34 -0
  133. package/lib/countries.ts +261 -0
  134. package/lib/formula.ts +630 -0
  135. package/lib/icons.ts +38 -0
  136. package/lib/loading.ts +75 -0
  137. package/lib/overlayBack.ts +124 -0
  138. package/lib/platform.ts +146 -0
  139. package/lib/primitive.ts +25 -0
  140. package/lib/q.ts +287 -0
  141. package/lib/screen.ts +105 -0
  142. package/lib/search.ts +35 -0
  143. package/lib/themeVars.ts +22 -0
  144. package/lib/useComponentProps.ts +54 -0
  145. package/lib/utils.ts +7 -0
  146. package/module.ts +89 -0
  147. package/package.json +41 -0
  148. package/scripts/generate-exports.ts +56 -0
  149. package/styles/main.css +7627 -0
  150. package/tsconfig.json +33 -0
@@ -0,0 +1,177 @@
1
+ <script lang="ts">
2
+ // QBottomSheet — bottom sheet type Drawer shadcn-vue (vaul), style Quasar :
3
+ // <q-bottom-sheet v-model="open"> + slot trigger / QBottomSheetHeader / QBottomSheetFooter
4
+ // Drag vers le bas pour fermer, backdrop, Échap, safe-area bottom.
5
+ import type { InjectionKey, Ref } from "vue"
6
+
7
+ export interface BottomSheetContext {
8
+ open: Readonly<Ref<boolean>>
9
+ setOpen: (v: boolean) => void
10
+ }
11
+
12
+ export const qBottomSheetKey: InjectionKey<BottomSheetContext> = Symbol("q-bottom-sheet")
13
+ </script>
14
+
15
+ <script setup lang="ts">
16
+ import { computed, onBeforeUnmount, onMounted, provide, ref, watch } from "vue"
17
+ import { cn } from "../lib/utils"
18
+ import { useOverlayBack } from "../lib/overlayBack"
19
+
20
+ interface Props {
21
+ /** Ouvert (v-model) */
22
+ modelValue?: boolean
23
+ /** Ne se ferme ni au backdrop ni à Échap */
24
+ persistent?: boolean
25
+ /** Largeur max du panneau */
26
+ width?: string
27
+ /** Hauteur du panneau (sinon max-height 90vh) */
28
+ height?: string
29
+ /** Arrondi des coins hauts : true | false | valeur CSS */
30
+ rounded?: boolean | string
31
+ dark?: boolean
32
+ /** Fond translucide (frosted glass) : true = 70%, ou valeur % */
33
+ translucent?: boolean | number
34
+ /** Seuil de drag (px) au-delà duquel on ferme */
35
+ dragThreshold?: number
36
+ /** Style du panneau (variables de thème pour les contenus téléportés) */
37
+ contentStyle?: Record<string, string>
38
+ }
39
+
40
+ const props = withDefaults(defineProps<Props>(), {
41
+ modelValue: false,
42
+ persistent: false,
43
+ width: "640px",
44
+ rounded: true,
45
+ dark: false,
46
+ dragThreshold: 80,
47
+ })
48
+
49
+ const emit = defineEmits<{ "update:modelValue": [value: boolean] }>()
50
+
51
+ const open = computed({
52
+ get: () => props.modelValue,
53
+ set: (v) => emit("update:modelValue", v),
54
+ })
55
+
56
+ provide<BottomSheetContext>(qBottomSheetKey, {
57
+ open,
58
+ setOpen: (v) => {
59
+ open.value = v
60
+ },
61
+ })
62
+
63
+ // « Retour » navigateur → ferme le bottom sheet au lieu de naviguer
64
+ useOverlayBack(open, () => { open.value = false }, "QBottomSheet")
65
+
66
+ // Échap + verrouillage du scroll du body
67
+ const onDocKeydown = (e: KeyboardEvent) => {
68
+ if (e.key === "Escape" && open.value && !props.persistent) open.value = false
69
+ }
70
+
71
+ watch(open, (v) => {
72
+ if (typeof document !== "undefined") document.body.style.overflow = v ? "hidden" : ""
73
+ })
74
+
75
+ onMounted(() => {
76
+ if (typeof document !== "undefined") document.addEventListener("keydown", onDocKeydown)
77
+ })
78
+ onBeforeUnmount(() => {
79
+ if (typeof document !== "undefined") {
80
+ document.removeEventListener("keydown", onDocKeydown)
81
+ document.body.style.overflow = ""
82
+ }
83
+ })
84
+
85
+ // — Drag to dismiss (pattern vaul) —
86
+ const panelRef = ref<HTMLElement | null>(null)
87
+ const dragging = ref(false)
88
+ let startY = 0
89
+ let currentDy = 0
90
+
91
+ const onHandleDown = (e: PointerEvent) => {
92
+ ;(e.target as HTMLElement).setPointerCapture(e.pointerId)
93
+ startY = e.clientY
94
+ currentDy = 0
95
+ dragging.value = true
96
+ }
97
+
98
+ const onHandleMove = (e: PointerEvent) => {
99
+ if (!dragging.value) return
100
+ currentDy = Math.max(0, e.clientY - startY)
101
+ if (panelRef.value) panelRef.value.style.transform = `translateY(${currentDy}px)`
102
+ }
103
+
104
+ const onHandleUp = () => {
105
+ if (!dragging.value) return
106
+ dragging.value = false
107
+ if (currentDy > props.dragThreshold) open.value = false
108
+ else if (panelRef.value) panelRef.value.style.transform = ""
109
+ currentDy = 0
110
+ }
111
+
112
+ const panelStyle = computed<Record<string, string>>(() => ({
113
+ maxWidth: props.width,
114
+ ...(props.height ? { height: props.height } : {}),
115
+ }))
116
+
117
+ const radiusStyle = computed<Record<string, string> | undefined>(() => {
118
+ if (props.rounded === false) return { borderRadius: "0" }
119
+ if (typeof props.rounded === "string") return { borderRadius: props.rounded }
120
+ return undefined
121
+ })
122
+
123
+ const panelClasses = computed(() =>
124
+ cn(
125
+ "q-bottom-sheet__panel",
126
+ props.rounded === false && "q-bottom-sheet__panel--square",
127
+ props.dark && "q-bottom-sheet__panel--dark",
128
+ (props.translucent === true || typeof props.translucent === "number") && "q-bottom-sheet__panel--translucent",
129
+ dragging.value && "q-bottom-sheet__panel--dragging",
130
+ ),
131
+ )
132
+
133
+ const translucentStyle = computed<Record<string, string> | undefined>(() =>
134
+ props.translucent === true || typeof props.translucent === "number"
135
+ ? { "--q-translucent-opacity": `${typeof props.translucent === "number" ? props.translucent : 70}%` }
136
+ : undefined,
137
+ )
138
+ </script>
139
+
140
+ <template>
141
+ <span v-if="$slots.trigger" class="q-bottom-sheet__trigger" @click="open = true">
142
+ <slot name="trigger" />
143
+ </span>
144
+
145
+ <Teleport to="body">
146
+ <Transition name="q-bs">
147
+ <div
148
+ v-if="open"
149
+ class="q-bottom-sheet__overlay"
150
+ @click="!persistent && (open = false)"
151
+ >
152
+ <div
153
+ ref="panelRef"
154
+ class="q-bottom-sheet__panel"
155
+ :class="panelClasses"
156
+ :style="[panelStyle, radiusStyle, translucentStyle, props.contentStyle]"
157
+ role="dialog"
158
+ aria-modal="true"
159
+ @click.stop
160
+ >
161
+ <div
162
+ class="q-bottom-sheet__handle"
163
+ @pointerdown="onHandleDown"
164
+ @pointermove="onHandleMove"
165
+ @pointerup="onHandleUp"
166
+ @pointercancel="onHandleUp"
167
+ >
168
+ <span class="q-bottom-sheet__handle-bar" />
169
+ </div>
170
+ <div class="q-bottom-sheet__body">
171
+ <slot />
172
+ </div>
173
+ </div>
174
+ </div>
175
+ </Transition>
176
+ </Teleport>
177
+ </template>
@@ -0,0 +1,9 @@
1
+ <script setup lang="ts">
2
+ // QBottomSheetFooter — zone d'actions en bas du panneau (équivalent DrawerFooter).
3
+ </script>
4
+
5
+ <template>
6
+ <div class="q-bottom-sheet__footer">
7
+ <slot />
8
+ </div>
9
+ </template>
@@ -0,0 +1,35 @@
1
+ <script setup lang="ts">
2
+ // QBottomSheetHeader — header du panneau : titre + description + bouton fermer (équivalent DrawerHeader/DrawerTitle).
3
+ import { inject } from "vue"
4
+ import { Icon } from "@iconify/vue"
5
+ import { icons } from "../lib/icons"
6
+ import { qBottomSheetKey } from "./QBottomSheet.vue"
7
+
8
+ interface Props {
9
+ title?: string
10
+ description?: string
11
+ }
12
+
13
+ const props = defineProps<Props>()
14
+ const sheet = inject(qBottomSheetKey, null)
15
+ </script>
16
+
17
+ <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 />
34
+ </div>
35
+ </template>
@@ -0,0 +1,62 @@
1
+ <script setup lang="ts">
2
+ // QBottomSheetProvider — rend la pile de bottom sheets programmatiques ($q.bottomSheet).
3
+ // Intégré dans QConfigProvider (rendu automatiquement par le plus externe),
4
+ // utilisable aussi en autonome : <q-bottom-sheet-provider><slot /></q-bottom-sheet-provider>.
5
+ import { computed, inject } from "vue"
6
+ import { bottomSheetStack, closeBottomSheet } from "../lib/q"
7
+ import type { BottomSheetController } from "../lib/q"
8
+ import { qConfigKey } from "../lib/config"
9
+ import { themeVarsStyle } from "../lib/themeVars"
10
+
11
+ const sheets = bottomSheetStack
12
+
13
+ const config = inject(qConfigKey, null)
14
+
15
+ // Téléporté au body : on repose les tokens du thème sur le panneau
16
+ const sheetThemeStyle = themeVarsStyle(computed(() => config?.theme.value ?? {}))
17
+
18
+ const handleOk = (ctrl: BottomSheetController, data?: unknown) => {
19
+ ctrl._resolvers.ok?.(data)
20
+ closeBottomSheet(ctrl)
21
+ }
22
+ const handleCancel = (ctrl: BottomSheetController) => {
23
+ ctrl._resolvers.cancel?.()
24
+ closeBottomSheet(ctrl)
25
+ }
26
+ const handleDismiss = (ctrl: BottomSheetController) => {
27
+ ctrl._resolvers.dismiss?.()
28
+ closeBottomSheet(ctrl)
29
+ }
30
+ </script>
31
+
32
+ <template>
33
+ <slot />
34
+ <q-bottom-sheet
35
+ v-for="(sheet, idx) in sheets"
36
+ :key="idx"
37
+ :model-value="true"
38
+ :persistent="sheet._opts.persistent"
39
+ :width="sheet._opts.width"
40
+ :height="sheet._opts.height"
41
+ :rounded="sheet._opts.rounded"
42
+ :dark="sheet._opts.dark"
43
+ :translucent="sheet._opts.translucent"
44
+ :drag-threshold="sheet._opts.dragThreshold"
45
+ :content-style="sheetThemeStyle"
46
+ @update:model-value="(v: boolean) => !v && handleDismiss(sheet)"
47
+ >
48
+ <q-bottom-sheet-header
49
+ v-if="sheet._opts.title || sheet._opts.description"
50
+ :title="sheet._opts.title"
51
+ :description="sheet._opts.description"
52
+ />
53
+ <component
54
+ :is="sheet._opts.component"
55
+ v-bind="sheet._opts.componentProps"
56
+ @ok="(data?: unknown) => handleOk(sheet, data)"
57
+ @cancel="handleCancel(sheet)"
58
+ @dismiss="handleDismiss(sheet)"
59
+ @close="handleDismiss(sheet)"
60
+ />
61
+ </q-bottom-sheet>
62
+ </template>
@@ -0,0 +1,18 @@
1
+ <script setup lang="ts">
2
+ // QBottomSheetTrigger — bouton d'ouverture (équivalent DrawerTrigger), alternative au slot #trigger.
3
+ import { inject } from "vue"
4
+ import { qBottomSheetKey } from "./QBottomSheet.vue"
5
+
6
+ interface Props {
7
+ label?: string
8
+ }
9
+
10
+ const props = defineProps<Props>()
11
+ const sheet = inject(qBottomSheetKey, null)
12
+ </script>
13
+
14
+ <template>
15
+ <button type="button" class="q-bottom-sheet__trigger-btn" @click="sheet?.setOpen(true)">
16
+ <slot>{{ label }}</slot>
17
+ </button>
18
+ </template>
@@ -0,0 +1,144 @@
1
+ <script setup lang="ts">
2
+ // QBtn — API Quasar : <q-btn flat dense outline rounded no-caps label="…" icon="…" />
3
+ // Implémentation shadcn-vue : cva (variants) + cn() ; styles dans styles/main.css
4
+ import { computed } from "vue"
5
+ import { Icon } from "@iconify/vue"
6
+ import { cva } from "class-variance-authority"
7
+ import { cn } from "../lib/utils"
8
+ import { radiusStyle, useRadius } from "../lib/useComponentProps"
9
+ import type { RadiusProp } from "../lib/useComponentProps"
10
+
11
+ const btnVariants = cva("q-btn", {
12
+ variants: {
13
+ size: {
14
+ sm: "q-btn--sm",
15
+ md: "q-btn--md",
16
+ lg: "q-btn--lg",
17
+ xl: "q-btn--xl",
18
+ },
19
+ },
20
+ defaultVariants: { size: "md" },
21
+ })
22
+
23
+ const TOKEN_COLORS = ["primary", "secondary", "accent", "dark", "positive", "negative", "info", "warning"] as const
24
+ const SIZE_TOKENS = ["sm", "md", "lg", "xl"] as const
25
+
26
+ interface Props {
27
+ /** Texte du bouton (sinon contenu du slot) */
28
+ label?: string
29
+ /** Icône Iconify à gauche du label (ex. : "lucide:download") */
30
+ icon?: string
31
+ /** Icône Iconify à droite du label */
32
+ iconRight?: string
33
+ /** Couleur : token (primary, secondary, negative…) ou hex (#1976d2) */
34
+ color?: string
35
+ /** Couleur du texte : token ou hex */
36
+ textColor?: string
37
+ /** Taille : token (sm|md|lg|xl) ou valeur CSS ("2rem") */
38
+ size?: string
39
+ type?: "button" | "submit" | "reset"
40
+ /** Rend un lien natif <a> */
41
+ href?: string
42
+ /** Fond transparent, texte coloré */
43
+ flat?: boolean
44
+ /** Fond transparent + bordure */
45
+ outline?: boolean
46
+ /** Supprime l'ombre portée */
47
+ unelevated?: boolean
48
+ /** Hauteur réduite */
49
+ dense?: boolean
50
+ /** Coins arrondis : true = pilule, ou échelle xs|sm|md|lg (none = carré) */
51
+ radius?: RadiusProp
52
+ /** Cercle parfait (bouton icône seule) */
53
+ round?: boolean
54
+ /** Coins droits */
55
+ square?: boolean
56
+ /** Désactive la mise en majuscules */
57
+ noCaps?: boolean
58
+ /** Largeur 100% */
59
+ stretch?: boolean
60
+ /** Affiche un spinner et bloque les clics */
61
+ loading?: boolean
62
+ /** Désactive le bouton */
63
+ disable?: boolean
64
+ }
65
+
66
+ const props = withDefaults(defineProps<Props>(), {
67
+ type: "button",
68
+ size: "md",
69
+ color: "primary",
70
+ flat: false,
71
+ outline: false,
72
+ unelevated: false,
73
+ dense: false,
74
+ round: false,
75
+ square: false,
76
+ noCaps: false,
77
+ stretch: false,
78
+ loading: false,
79
+ disable: false,
80
+ })
81
+
82
+ const isTokenColor = (c?: string) => !!c && (TOKEN_COLORS as readonly string[]).includes(c)
83
+ const isSizeToken = (s?: string) => !!s && (SIZE_TOKENS as readonly string[]).includes(s)
84
+
85
+ const sizeVariant = computed(() =>
86
+ isSizeToken(props.size) ? (props.size as "sm" | "md" | "lg" | "xl") : "md",
87
+ )
88
+
89
+ /** Taille CSS libre : passée en variable CSS --q-btn-h */
90
+ const sizeStyle = computed<Record<string, string>>((): Record<string, string> =>
91
+ props.size && !isSizeToken(props.size) ? { "--q-btn-h": props.size } : {},
92
+ )
93
+
94
+ /** Couleurs hex libres : passées en variables CSS */
95
+ const colorStyle = computed<Record<string, string>>(() => {
96
+ const style: Record<string, string> = {}
97
+ if (props.color && !isTokenColor(props.color)) style["--q-btn-bg"] = props.color
98
+ if (props.textColor && !isTokenColor(props.textColor)) style["--q-btn-fg"] = props.textColor
99
+ return style
100
+ })
101
+
102
+ const isDisabled = computed(() => props.disable || props.loading)
103
+
104
+ // radius : prop explicite > composantProps.QBtn.radius ; échelle → --q-radius
105
+ const effectiveRadius = useRadius("QBtn", () => props.radius)
106
+ const roundedStyle = computed(() => radiusStyle(effectiveRadius.value))
107
+
108
+ const btnClasses = computed(() =>
109
+ cn(
110
+ btnVariants({ size: sizeVariant.value }),
111
+ isTokenColor(props.color) && `q-btn--${props.color}`,
112
+ isTokenColor(props.textColor) && `q-btn--text-${props.textColor}`,
113
+ props.flat && "q-btn--flat",
114
+ props.outline && "q-btn--outline",
115
+ props.unelevated && "q-btn--unelevated",
116
+ props.dense && "q-btn--dense",
117
+ effectiveRadius.value === true && "q-btn--rounded",
118
+ props.round && "q-btn--round",
119
+ props.square && "q-btn--square",
120
+ props.noCaps && "q-btn--no-caps",
121
+ props.stretch && "q-btn--stretch",
122
+ props.loading && "q-btn--loading",
123
+ isDisabled.value && "q-btn--disabled",
124
+ ),
125
+ )
126
+ </script>
127
+
128
+ <template>
129
+ <component
130
+ :is="href ? 'a' : 'button'"
131
+ :href="href || undefined"
132
+ :type="href ? undefined : type"
133
+ :disabled="href ? undefined : isDisabled"
134
+ :aria-disabled="isDisabled || undefined"
135
+ :class="btnClasses"
136
+ :style="[sizeStyle, colorStyle, roundedStyle]"
137
+ >
138
+ <span v-if="loading" class="q-btn__spinner" aria-hidden="true" />
139
+ <Icon v-else-if="icon" :icon="icon" class="q-btn__icon" aria-hidden="true" />
140
+ <span v-if="label" class="q-btn__label">{{ label }}</span>
141
+ <slot />
142
+ <Icon v-if="iconRight && !loading" :icon="iconRight" class="q-btn__icon" aria-hidden="true" />
143
+ </component>
144
+ </template>
@@ -0,0 +1,31 @@
1
+ <script setup lang="ts">
2
+ // QBtnGroup — API Quasar : <q-btn-group unelevated outline stretch>
3
+ // Groupe de QBtn soudés (bords fusionnés, rayons sur les extrémités).
4
+ interface Props {
5
+ /** Boutons sans ombre */
6
+ unelevated?: boolean
7
+ /** Contour de groupe */
8
+ outline?: boolean
9
+ /** Les boutons s'étendent également */
10
+ stretch?: boolean
11
+ }
12
+
13
+ const props = withDefaults(defineProps<Props>(), {
14
+ unelevated: false,
15
+ outline: false,
16
+ stretch: false,
17
+ })
18
+ </script>
19
+
20
+ <template>
21
+ <div
22
+ class="q-btn-group"
23
+ :class="{
24
+ 'q-btn-group--unelevated': props.unelevated,
25
+ 'q-btn-group--outline': props.outline,
26
+ 'q-btn-group--stretch': props.stretch,
27
+ }"
28
+ >
29
+ <slot />
30
+ </div>
31
+ </template>
@@ -0,0 +1,36 @@
1
+ <script setup lang="ts">
2
+ // QBubble — bulle de conversation type Bubble shadcn-vue :
3
+ // variants default | secondary | muted | tinted | outline | ghost | destructive, align start|end.
4
+ import { computed } from "vue"
5
+ import { cn } from "../lib/utils"
6
+ import { QPrimitive } from "../lib/primitive"
7
+
8
+ interface Props {
9
+ /** Traitement visuel */
10
+ variant?: "default" | "secondary" | "muted" | "tinted" | "outline" | "ghost" | "destructive"
11
+ /** Alignement dans la conversation */
12
+ align?: "start" | "end"
13
+ /** Élément/composant rendu */
14
+ as?: string
15
+ /** Fusion sur l'enfant du slot (bouton, lien…) */
16
+ asChild?: boolean
17
+ class?: string
18
+ }
19
+
20
+ const props = withDefaults(defineProps<Props>(), {
21
+ variant: "default",
22
+ align: "start",
23
+ as: "div",
24
+ asChild: false,
25
+ })
26
+
27
+ const classes = computed(() =>
28
+ cn("q-bubble", `q-bubble--${props.variant}`, `q-bubble--align-${props.align}`, props.class),
29
+ )
30
+ </script>
31
+
32
+ <template>
33
+ <QPrimitive :as="as" :as-child="asChild" :class="classes">
34
+ <slot />
35
+ </QPrimitive>
36
+ </template>
@@ -0,0 +1,28 @@
1
+ <script setup lang="ts">
2
+ // QBubbleContent — contenu de la bulle (équivalent BubbleContent).
3
+ // as-child : le conteneur (bouton/lien) reçoit le style de la bulle.
4
+ import { computed } from "vue"
5
+ import { cn } from "../lib/utils"
6
+ import { QPrimitive } from "../lib/primitive"
7
+
8
+ interface Props {
9
+ /** Élément/composant rendu */
10
+ as?: string
11
+ /** Fusion sur l'enfant du slot */
12
+ asChild?: boolean
13
+ class?: string
14
+ }
15
+
16
+ const props = withDefaults(defineProps<Props>(), {
17
+ as: "div",
18
+ asChild: false,
19
+ })
20
+
21
+ const classes = computed(() => cn("q-bubble__content", props.class))
22
+ </script>
23
+
24
+ <template>
25
+ <QPrimitive :as="as" :as-child="asChild" :class="classes">
26
+ <slot />
27
+ </QPrimitive>
28
+ </template>
@@ -0,0 +1,25 @@
1
+ <script setup lang="ts">
2
+ // QBubbleGroup — groupe de bulles consécutives du même expéditeur (équivalent BubbleGroup).
3
+ import { computed } from "vue"
4
+ import { cn } from "../lib/utils"
5
+ import { QPrimitive } from "../lib/primitive"
6
+
7
+ interface Props {
8
+ as?: string
9
+ asChild?: boolean
10
+ class?: string
11
+ }
12
+
13
+ const props = withDefaults(defineProps<Props>(), {
14
+ as: "div",
15
+ asChild: false,
16
+ })
17
+
18
+ const classes = computed(() => cn("q-bubble-group", props.class))
19
+ </script>
20
+
21
+ <template>
22
+ <QPrimitive :as="as" :as-child="asChild" :class="classes">
23
+ <slot />
24
+ </QPrimitive>
25
+ </template>
@@ -0,0 +1,40 @@
1
+ <script setup lang="ts">
2
+ // QBubbleReactions — réactions ancrées au bord de la bulle (équivalent BubbleReactions).
3
+ // side : top | bottom (chevauché) ; align : start | end.
4
+ import { computed } from "vue"
5
+ import { cn } from "../lib/utils"
6
+ import { QPrimitive } from "../lib/primitive"
7
+
8
+ interface Props {
9
+ /** Côté de la bulle où ancrer les réactions */
10
+ side?: "top" | "bottom"
11
+ /** Alignement inline */
12
+ align?: "start" | "end"
13
+ /** Élément/composant rendu */
14
+ as?: string
15
+ asChild?: boolean
16
+ class?: string
17
+ }
18
+
19
+ const props = withDefaults(defineProps<Props>(), {
20
+ side: "bottom",
21
+ align: "end",
22
+ as: "div",
23
+ asChild: false,
24
+ })
25
+
26
+ const classes = computed(() =>
27
+ cn(
28
+ "q-bubble__reactions",
29
+ props.align === "start" && "q-bubble__reactions--start",
30
+ props.side === "top" && "q-bubble__reactions--top",
31
+ props.class,
32
+ ),
33
+ )
34
+ </script>
35
+
36
+ <template>
37
+ <QPrimitive :as="as" :as-child="asChild" :class="classes">
38
+ <slot />
39
+ </QPrimitive>
40
+ </template>
@@ -0,0 +1,63 @@
1
+ <script setup lang="ts">
2
+ // QCard — API Quasar : <q-card elevated bordered dark square rounded="lg">
3
+ // Pas d'ombre par défaut ; elevated l'ajoute, flat la retire explicitement.
4
+ // Slots : default, media (zone image en haut), actions (zone actions en bas)
5
+ import { computed } from "vue"
6
+ import { radiusStyle, useRadius } from "../lib/useComponentProps"
7
+ import type { RadiusProp } from "../lib/useComponentProps"
8
+
9
+ interface Props {
10
+ /** Ajoute l'ombre portée (défaut : aucune) */
11
+ elevated?: boolean
12
+ /** Retire explicitement l'ombre */
13
+ flat?: boolean
14
+ /** Thème sombre */
15
+ dark?: boolean
16
+ /** Bordure fine */
17
+ bordered?: boolean
18
+ /** Coins droits */
19
+ square?: boolean
20
+ /** Coins arrondis : true = md, ou échelle xs|sm|md|lg (none = carré) */
21
+ radius?: RadiusProp
22
+ /** Glassmorphism : fond translucide + flou d'arrière-plan (backdrop-filter) */
23
+ glass?: boolean
24
+ /** Animation au survol : élévation + ombre renforcée */
25
+ hover?: boolean
26
+ }
27
+
28
+ const props = withDefaults(defineProps<Props>(), {
29
+ elevated: false,
30
+ flat: false,
31
+ dark: false,
32
+ bordered: false,
33
+ square: false,
34
+ glass: false,
35
+ hover: false,
36
+ })
37
+
38
+ // radius : prop explicite > composantProps.QCard.radius ; échelle → --q-radius
39
+ const effectiveRadius = useRadius("QCard", () => props.radius)
40
+ const roundedStyle = computed(() => radiusStyle(effectiveRadius.value))
41
+ </script>
42
+
43
+ <template>
44
+ <div
45
+ class="q-card"
46
+ :class="{
47
+ 'q-card--elevated': props.elevated,
48
+ 'q-card--flat': props.flat,
49
+ 'q-card--dark': props.dark,
50
+ 'q-card--bordered': props.bordered,
51
+ 'q-card--square': props.square,
52
+ 'q-card--glass': props.glass,
53
+ 'q-card--hover': props.hover,
54
+ }"
55
+ :style="roundedStyle"
56
+ >
57
+ <slot name="media" />
58
+ <slot />
59
+ <div v-if="$slots.actions" class="q-card__actions-slot">
60
+ <slot name="actions" />
61
+ </div>
62
+ </div>
63
+ </template>