@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.
- package/README.md +15 -0
- package/components/QAccordion.vue +68 -0
- package/components/QAccordionContent.vue +62 -0
- package/components/QAccordionItem.vue +24 -0
- package/components/QAccordionTrigger.vue +51 -0
- package/components/QActionSheet.vue +159 -0
- package/components/QApp.vue +8 -0
- package/components/QAutocomplete.vue +316 -0
- package/components/QAvatar.vue +92 -0
- package/components/QBackHeader.vue +69 -0
- package/components/QBackTop.vue +125 -0
- package/components/QBadge.vue +55 -0
- package/components/QBottomSheet.vue +177 -0
- package/components/QBottomSheetFooter.vue +9 -0
- package/components/QBottomSheetHeader.vue +35 -0
- package/components/QBottomSheetProvider.vue +62 -0
- package/components/QBottomSheetTrigger.vue +18 -0
- package/components/QBtn.vue +144 -0
- package/components/QBtnGroup.vue +31 -0
- package/components/QBubble.vue +36 -0
- package/components/QBubbleContent.vue +28 -0
- package/components/QBubbleGroup.vue +25 -0
- package/components/QBubbleReactions.vue +40 -0
- package/components/QCard.vue +63 -0
- package/components/QCardActions.vue +27 -0
- package/components/QCardContent.vue +29 -0
- package/components/QCarousel.vue +115 -0
- package/components/QCarouselContent.vue +23 -0
- package/components/QCarouselItem.vue +20 -0
- package/components/QCarouselNav.vue +51 -0
- package/components/QCheckbox.vue +146 -0
- package/components/QChip.vue +91 -0
- package/components/QCircularProgress.vue +122 -0
- package/components/QCol.vue +52 -0
- package/components/QCollapse.vue +141 -0
- package/components/QConfigProvider.vue +164 -0
- package/components/QContainer.vue +201 -0
- package/components/QCountDown.vue +164 -0
- package/components/QCountryPicker.vue +593 -0
- package/components/QDataGrid.vue +491 -0
- package/components/QDatePicker.vue +250 -0
- package/components/QDialog.vue +163 -0
- package/components/QDialogFooter.vue +9 -0
- package/components/QDialogHeader.vue +41 -0
- package/components/QDialogProvider.vue +65 -0
- package/components/QDialogTrigger.vue +18 -0
- package/components/QFab.vue +80 -0
- package/components/QFabAction.vue +48 -0
- package/components/QFilePicker.vue +294 -0
- package/components/QFooter.vue +72 -0
- package/components/QGallery.vue +280 -0
- package/components/QGrid.vue +54 -0
- package/components/QHeader.vue +38 -0
- package/components/QIcon.vue +53 -0
- package/components/QImagePicker.vue +248 -0
- package/components/QImagePreview.vue +225 -0
- package/components/QImagePreviewProvider.vue +35 -0
- package/components/QImg.vue +142 -0
- package/components/QInfiniteScroll.vue +109 -0
- package/components/QInnerLoading.vue +67 -0
- package/components/QInput.vue +289 -0
- package/components/QInputOtp.vue +238 -0
- package/components/QInputPassword.vue +67 -0
- package/components/QInputTag.vue +281 -0
- package/components/QIntersection.vue +119 -0
- package/components/QItem.vue +60 -0
- package/components/QItemSection.vue +47 -0
- package/components/QLinearProgress.vue +94 -0
- package/components/QList.vue +39 -0
- package/components/QLoading.vue +77 -0
- package/components/QLoadingProvider.vue +41 -0
- package/components/QMarquee.vue +56 -0
- package/components/QMessageScroller.vue +10 -0
- package/components/QMessageScrollerButton.vue +48 -0
- package/components/QMessageScrollerContent.vue +31 -0
- package/components/QMessageScrollerItem.vue +49 -0
- package/components/QMessageScrollerProvider.vue +198 -0
- package/components/QMessageScrollerViewport.vue +109 -0
- package/components/QNavMenu.vue +75 -0
- package/components/QNavMenuContent.vue +10 -0
- package/components/QNavMenuItem.vue +60 -0
- package/components/QNavMenuTrigger.vue +96 -0
- package/components/QNotifyProvider.vue +33 -0
- package/components/QNotifyToast.vue +60 -0
- package/components/QPage.vue +56 -0
- package/components/QPagination.vue +195 -0
- package/components/QParallax.vue +124 -0
- package/components/QPullToRefresh.vue +138 -0
- package/components/QRadio.vue +99 -0
- package/components/QRating.vue +105 -0
- package/components/QReorder.vue +191 -0
- package/components/QRollingText.vue +225 -0
- package/components/QRow.vue +23 -0
- package/components/QSafeArea.vue +39 -0
- package/components/QScrollArea.vue +118 -0
- package/components/QSelect.vue +698 -0
- package/components/QSeparator.vue +39 -0
- package/components/QSidebar.vue +153 -0
- package/components/QSidebarContent.vue +14 -0
- package/components/QSidebarFooter.vue +14 -0
- package/components/QSidebarHeader.vue +14 -0
- package/components/QSidebarMenu.vue +9 -0
- package/components/QSidebarMenuButton.vue +59 -0
- package/components/QSidebarMenuItem.vue +9 -0
- package/components/QSidebarTrigger.vue +32 -0
- package/components/QSkeleton.vue +95 -0
- package/components/QSlider.vue +205 -0
- package/components/QSpace.vue +10 -0
- package/components/QSpinner.vue +172 -0
- package/components/QSplitter.vue +145 -0
- package/components/QSticky.vue +181 -0
- package/components/QSwipeCell.vue +217 -0
- package/components/QSwiper.vue +102 -0
- package/components/QSwiperSlide.vue +19 -0
- package/components/QSyntax.vue +171 -0
- package/components/QTab.vue +175 -0
- package/components/QTabPanel.vue +50 -0
- package/components/QTabPanels.vue +106 -0
- package/components/QTable.vue +575 -0
- package/components/QTabs.vue +188 -0
- package/components/QText.vue +316 -0
- package/components/QTextCaption.vue +122 -0
- package/components/QToolbar.vue +28 -0
- package/components/QTooltip.vue +313 -0
- package/components/QUploader.vue +473 -0
- package/components/QVideo.vue +289 -0
- package/components/QVirtualScroll.vue +190 -0
- package/components/internal/QDateCalendar.vue +153 -0
- package/index.ts +151 -0
- package/lib/breakpoints.ts +44 -0
- package/lib/colors.ts +24 -0
- package/lib/config.ts +34 -0
- package/lib/countries.ts +261 -0
- package/lib/formula.ts +630 -0
- package/lib/icons.ts +38 -0
- package/lib/loading.ts +75 -0
- package/lib/overlayBack.ts +124 -0
- package/lib/platform.ts +146 -0
- package/lib/primitive.ts +25 -0
- package/lib/q.ts +287 -0
- package/lib/screen.ts +105 -0
- package/lib/search.ts +35 -0
- package/lib/themeVars.ts +22 -0
- package/lib/useComponentProps.ts +54 -0
- package/lib/utils.ts +7 -0
- package/module.ts +89 -0
- package/package.json +41 -0
- package/scripts/generate-exports.ts +56 -0
- package/styles/main.css +7627 -0
- package/tsconfig.json +33 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// QItem — API Quasar : <q-item clickable active active-class="…" dense disable>
|
|
3
|
+
import { cn } from "../lib/utils"
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
/** Clic + hover (cursor pointer) */
|
|
7
|
+
clickable?: boolean
|
|
8
|
+
/** Hauteur réduite */
|
|
9
|
+
dense?: boolean
|
|
10
|
+
/** État actif */
|
|
11
|
+
active?: boolean
|
|
12
|
+
/** Classe ajoutée quand actif */
|
|
13
|
+
activeClass?: string
|
|
14
|
+
/** Désactivé */
|
|
15
|
+
disable?: boolean
|
|
16
|
+
tabindex?: string | number
|
|
17
|
+
/** Élément rendu (div par défaut) */
|
|
18
|
+
tag?: string
|
|
19
|
+
/** Indentation (multiples de 8px) */
|
|
20
|
+
insetLevel?: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
24
|
+
clickable: false,
|
|
25
|
+
dense: false,
|
|
26
|
+
active: false,
|
|
27
|
+
activeClass: "",
|
|
28
|
+
disable: false,
|
|
29
|
+
tag: "div",
|
|
30
|
+
insetLevel: 0,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const emit = defineEmits<{ click: [event: MouseEvent] }>()
|
|
34
|
+
|
|
35
|
+
const itemClasses = cn(
|
|
36
|
+
"q-item",
|
|
37
|
+
props.clickable && "q-item--clickable",
|
|
38
|
+
props.dense && "q-item--dense",
|
|
39
|
+
props.active && "q-item--active",
|
|
40
|
+
props.active && props.activeClass,
|
|
41
|
+
props.disable && "q-item--disabled",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
const itemStyle = props.insetLevel > 0 ? { paddingLeft: `${8 + props.insetLevel * 8}px` } : {}
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<template>
|
|
48
|
+
<component
|
|
49
|
+
:is="tag"
|
|
50
|
+
class="q-item"
|
|
51
|
+
:class="itemClasses"
|
|
52
|
+
:style="itemStyle"
|
|
53
|
+
:tabindex="clickable ? (tabindex ?? 0) : undefined"
|
|
54
|
+
role="listitem"
|
|
55
|
+
:aria-disabled="disable || undefined"
|
|
56
|
+
@click="(e: MouseEvent) => { if (!disable) emit('click', e) }"
|
|
57
|
+
>
|
|
58
|
+
<slot />
|
|
59
|
+
</component>
|
|
60
|
+
</template>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// QItemSection — API Quasar : <q-item-section avatar side top no-wrap>
|
|
3
|
+
// Sections d'un QItem : principale (flex:1), side (droite), avatar, thumbnail.
|
|
4
|
+
import { cn } from "../lib/utils"
|
|
5
|
+
import { colorValue } from "../lib/colors"
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
/** Zone avatar (largeur fixe, pas de stretch) */
|
|
9
|
+
avatar?: boolean
|
|
10
|
+
/** Zone vignette/image */
|
|
11
|
+
thumbnail?: boolean
|
|
12
|
+
/** Alignée à droite (actions…) */
|
|
13
|
+
side?: boolean
|
|
14
|
+
/** Alignée en haut */
|
|
15
|
+
top?: boolean
|
|
16
|
+
/** Pas de retour à la ligne */
|
|
17
|
+
noWrap?: boolean
|
|
18
|
+
/** Couleur du texte (token ou hex) */
|
|
19
|
+
color?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
23
|
+
avatar: false,
|
|
24
|
+
thumbnail: false,
|
|
25
|
+
side: false,
|
|
26
|
+
top: false,
|
|
27
|
+
noWrap: false,
|
|
28
|
+
color: "",
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const sectionClasses = cn(
|
|
32
|
+
"q-item__section",
|
|
33
|
+
props.avatar && "q-item__section--avatar",
|
|
34
|
+
props.thumbnail && "q-item__section--thumbnail",
|
|
35
|
+
props.side && "q-item__section--side",
|
|
36
|
+
props.top && "q-item__section--top",
|
|
37
|
+
props.noWrap && "q-item__section--no-wrap",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
const sectionStyle = props.color ? { color: colorValue(props.color) } : {}
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<template>
|
|
44
|
+
<div class="q-item__section" :class="sectionClasses" :style="sectionStyle">
|
|
45
|
+
<slot />
|
|
46
|
+
</div>
|
|
47
|
+
</template>
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// QLinearProgress — barre de progression linéaire : <q-linear-progress value="0.7" color="secondary" stripe />
|
|
3
|
+
import { computed } from "vue"
|
|
4
|
+
import { cn } from "../lib/utils"
|
|
5
|
+
import { colorValue } from "../lib/colors"
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
/** Valeur (entre min et max, défaut 0-1) */
|
|
9
|
+
value?: number
|
|
10
|
+
min?: number
|
|
11
|
+
max?: number
|
|
12
|
+
/** Couleur de la barre (token ou hex) */
|
|
13
|
+
color?: string
|
|
14
|
+
/** Couleur de la piste */
|
|
15
|
+
trackColor?: string
|
|
16
|
+
/** Épaisseur (défaut 4px) */
|
|
17
|
+
size?: string
|
|
18
|
+
rounded?: boolean
|
|
19
|
+
/** Rayures animées */
|
|
20
|
+
stripe?: boolean
|
|
21
|
+
/** Sens inverse (remplit depuis la droite) */
|
|
22
|
+
reverse?: boolean
|
|
23
|
+
/** Mode indéterminé (bande qui traverse) */
|
|
24
|
+
indeterminate?: boolean
|
|
25
|
+
/** Mode recherche (bande qui traverse en sens inverse) */
|
|
26
|
+
query?: boolean
|
|
27
|
+
dark?: boolean
|
|
28
|
+
/** Sans transition sur width */
|
|
29
|
+
instantFeedback?: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
33
|
+
value: 0,
|
|
34
|
+
min: 0,
|
|
35
|
+
max: 1,
|
|
36
|
+
color: "primary",
|
|
37
|
+
trackColor: "",
|
|
38
|
+
size: "4px",
|
|
39
|
+
rounded: false,
|
|
40
|
+
stripe: false,
|
|
41
|
+
reverse: false,
|
|
42
|
+
indeterminate: false,
|
|
43
|
+
query: false,
|
|
44
|
+
dark: false,
|
|
45
|
+
instantFeedback: false,
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const ratio = computed(() => {
|
|
49
|
+
const span = props.max - props.min
|
|
50
|
+
if (span <= 0) return 0
|
|
51
|
+
return Math.min(1, Math.max(0, (props.value - props.min) / span))
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const trackStyle = computed<Record<string, string>>(() => {
|
|
55
|
+
const style: Record<string, string> = { height: props.size }
|
|
56
|
+
if (props.trackColor) style.backgroundColor = colorValue(props.trackColor)
|
|
57
|
+
return style
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const fillStyle = computed<Record<string, string>>(() => {
|
|
61
|
+
const style: Record<string, string> = { backgroundColor: colorValue(props.color) }
|
|
62
|
+
if (!props.indeterminate && !props.query) {
|
|
63
|
+
style.width = `${ratio.value * 100}%`
|
|
64
|
+
}
|
|
65
|
+
return style
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const classes = computed(() =>
|
|
69
|
+
cn(
|
|
70
|
+
"q-linear-progress",
|
|
71
|
+
props.rounded && "q-linear-progress--rounded",
|
|
72
|
+
props.dark && "q-linear-progress--dark",
|
|
73
|
+
props.stripe && "q-linear-progress--stripe",
|
|
74
|
+
props.reverse && "q-linear-progress--reverse",
|
|
75
|
+
props.indeterminate && "q-linear-progress--indeterminate",
|
|
76
|
+
props.query && "q-linear-progress--query",
|
|
77
|
+
props.instantFeedback && "q-linear-progress--instant",
|
|
78
|
+
),
|
|
79
|
+
)
|
|
80
|
+
</script>
|
|
81
|
+
|
|
82
|
+
<template>
|
|
83
|
+
<div
|
|
84
|
+
class="q-linear-progress"
|
|
85
|
+
:class="classes"
|
|
86
|
+
:style="trackStyle"
|
|
87
|
+
role="progressbar"
|
|
88
|
+
:aria-valuenow="value"
|
|
89
|
+
:aria-valuemin="min"
|
|
90
|
+
:aria-valuemax="max"
|
|
91
|
+
>
|
|
92
|
+
<div class="q-linear-progress__fill" :style="fillStyle" />
|
|
93
|
+
</div>
|
|
94
|
+
</template>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// QList — API Quasar : <q-list bordered dense separator dark padding>
|
|
3
|
+
interface Props {
|
|
4
|
+
/** Bordure fine */
|
|
5
|
+
bordered?: boolean
|
|
6
|
+
/** Hauteur réduite des items */
|
|
7
|
+
dense?: boolean
|
|
8
|
+
/** Séparateurs entre items */
|
|
9
|
+
separator?: boolean
|
|
10
|
+
/** Thème sombre */
|
|
11
|
+
dark?: boolean
|
|
12
|
+
/** Padding plus important */
|
|
13
|
+
padding?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
17
|
+
bordered: false,
|
|
18
|
+
dense: false,
|
|
19
|
+
separator: false,
|
|
20
|
+
dark: false,
|
|
21
|
+
padding: false,
|
|
22
|
+
})
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<template>
|
|
26
|
+
<div
|
|
27
|
+
class="q-list"
|
|
28
|
+
:class="{
|
|
29
|
+
'q-list--bordered': props.bordered,
|
|
30
|
+
'q-list--dense': props.dense,
|
|
31
|
+
'q-list--separator': props.separator,
|
|
32
|
+
'q-list--dark': props.dark,
|
|
33
|
+
'q-list--padding': props.padding,
|
|
34
|
+
}"
|
|
35
|
+
role="list"
|
|
36
|
+
>
|
|
37
|
+
<slot />
|
|
38
|
+
</div>
|
|
39
|
+
</template>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// QLoading — overlay de chargement plein écran déclaratif : <q-loading v-model="busy" message="…" icon="…" />
|
|
3
|
+
// Alternative au plugin $q.loading (mêmes options visuelles) — piloté par v-model.
|
|
4
|
+
import { computed } from "vue"
|
|
5
|
+
import { Icon } from "@iconify/vue"
|
|
6
|
+
import { colorValue } from "../lib/colors"
|
|
7
|
+
import { useOverlayBack } from "../lib/overlayBack"
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
/** Affiche/masque l'overlay (v-model) */
|
|
11
|
+
modelValue?: boolean
|
|
12
|
+
/** Message sous le spinner */
|
|
13
|
+
message?: string
|
|
14
|
+
/** Taille du spinner en px (défaut : 48) */
|
|
15
|
+
spinnerSize?: number
|
|
16
|
+
/** Couleur du spinner : token (primary…) ou hex (défaut : primary) */
|
|
17
|
+
spinnerColor?: string
|
|
18
|
+
/** Couleur du message (défaut : #1d1d1d) */
|
|
19
|
+
messageColor?: string
|
|
20
|
+
/** Fond de l'overlay (défaut : rgb(0 0 0 / 0.3)) */
|
|
21
|
+
backgroundColor?: string
|
|
22
|
+
/** Icône Iconify affichée à la place du spinner (ex. : "lucide:loader-circle") */
|
|
23
|
+
icon?: string
|
|
24
|
+
/** Couleur de l'icône (défaut : spinnerColor) */
|
|
25
|
+
iconColor?: string
|
|
26
|
+
/** Garde la carte (fond blanc + ombre) même avec une icône (défaut : transparent) */
|
|
27
|
+
boxed?: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
31
|
+
modelValue: false,
|
|
32
|
+
spinnerSize: 48,
|
|
33
|
+
spinnerColor: "primary",
|
|
34
|
+
messageColor: "#1d1d1d",
|
|
35
|
+
backgroundColor: "rgb(0 0 0 / 0.3)",
|
|
36
|
+
icon: "",
|
|
37
|
+
iconColor: "",
|
|
38
|
+
boxed: false,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const emit = defineEmits<{ "update:modelValue": [value: boolean] }>()
|
|
42
|
+
|
|
43
|
+
// « Retour » navigateur → ferme l'overlay de chargement au lieu de naviguer
|
|
44
|
+
const open = computed({
|
|
45
|
+
get: () => props.modelValue,
|
|
46
|
+
set: (v) => emit("update:modelValue", v),
|
|
47
|
+
})
|
|
48
|
+
useOverlayBack(open, () => { open.value = false }, "QLoading")
|
|
49
|
+
|
|
50
|
+
const spinnerStyle = computed(() => ({
|
|
51
|
+
width: `${props.spinnerSize}px`,
|
|
52
|
+
height: `${props.spinnerSize}px`,
|
|
53
|
+
borderWidth: `${Math.max(2, Math.round(props.spinnerSize / 8))}px`,
|
|
54
|
+
color: colorValue(props.spinnerColor),
|
|
55
|
+
}))
|
|
56
|
+
|
|
57
|
+
const iconStyle = computed(() => ({
|
|
58
|
+
width: `${props.spinnerSize}px`,
|
|
59
|
+
height: `${props.spinnerSize}px`,
|
|
60
|
+
color: colorValue(props.iconColor || props.spinnerColor),
|
|
61
|
+
}))
|
|
62
|
+
|
|
63
|
+
const messageStyle = computed(() => ({ color: colorValue(props.messageColor) }))
|
|
64
|
+
const overlayStyle = computed(() => ({ backgroundColor: props.backgroundColor }))
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<template>
|
|
68
|
+
<Teleport to="body">
|
|
69
|
+
<div v-if="modelValue" class="q-loading" :style="overlayStyle" role="status" aria-live="polite">
|
|
70
|
+
<div class="q-loading__box" :class="{ 'q-loading__box--icon': icon && !boxed }">
|
|
71
|
+
<span v-if="!icon" class="q-loading__spinner" :style="spinnerStyle" aria-hidden="true" />
|
|
72
|
+
<Icon v-else :icon="icon" class="q-loading__icon" :style="iconStyle" aria-hidden="true" />
|
|
73
|
+
<div v-if="message" class="q-loading__message" :style="messageStyle">{{ message }}</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</Teleport>
|
|
77
|
+
</template>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// QLoadingProvider — overlay de chargement plein écran ($q.loading).
|
|
3
|
+
// Intégré dans QConfigProvider (rendu automatiquement par le plus externe),
|
|
4
|
+
// utilisable aussi en autonome : <q-loading-provider>.
|
|
5
|
+
import { computed } from "vue"
|
|
6
|
+
import { Icon } from "@iconify/vue"
|
|
7
|
+
import { colorValue } from "../lib/colors"
|
|
8
|
+
import { loading } from "../lib/loading"
|
|
9
|
+
|
|
10
|
+
const visible = loading.isActive
|
|
11
|
+
const opts = loading._opts
|
|
12
|
+
|
|
13
|
+
const spinnerStyle = computed(() => ({
|
|
14
|
+
width: `${opts.spinnerSize}px`,
|
|
15
|
+
height: `${opts.spinnerSize}px`,
|
|
16
|
+
borderWidth: `${Math.max(2, Math.round((opts.spinnerSize ?? 48) / 8))}px`,
|
|
17
|
+
color: colorValue(opts.spinnerColor ?? "primary"),
|
|
18
|
+
}))
|
|
19
|
+
|
|
20
|
+
const iconStyle = computed(() => ({
|
|
21
|
+
width: `${opts.spinnerSize}px`,
|
|
22
|
+
height: `${opts.spinnerSize}px`,
|
|
23
|
+
color: colorValue(opts.iconColor ?? opts.spinnerColor ?? "primary"),
|
|
24
|
+
}))
|
|
25
|
+
|
|
26
|
+
const messageStyle = computed(() => ({ color: colorValue(opts.messageColor ?? "#1d1d1d") }))
|
|
27
|
+
const overlayStyle = computed(() => ({ backgroundColor: opts.backgroundColor }))
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<template>
|
|
31
|
+
<slot />
|
|
32
|
+
<div v-if="visible" class="q-loading" :style="overlayStyle" role="status" aria-live="polite">
|
|
33
|
+
<div class="q-loading__box" :class="{ 'q-loading__box--icon': opts.icon && !opts.boxed }">
|
|
34
|
+
<span v-if="!opts.icon" class="q-loading__spinner" :style="spinnerStyle" aria-hidden="true" />
|
|
35
|
+
<Icon :icon="opts.icon" v-else class="q-loading__icon" :style="iconStyle" aria-hidden="true" />
|
|
36
|
+
<div v-if="opts.message" class="q-loading__message" :style="messageStyle">
|
|
37
|
+
{{ opts.message }}
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</template>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// QMarquee — bandeau défilant infini : <q-marquee :text="…" :pause-on-hover="true" />.
|
|
3
|
+
// Deux copies du contenu (track animé translateX -50%) pour une boucle parfaite.
|
|
4
|
+
// Props : text (ou slot), direction (left|right), duration (s), gap, pause-on-hover.
|
|
5
|
+
import { computed } from "vue"
|
|
6
|
+
import { cn } from "../lib/utils"
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
/** Texte (sinon slot par défaut, rendu 2× pour la boucle) */
|
|
10
|
+
text?: string
|
|
11
|
+
/** Sens de défilement : "left" (vers la gauche) | "right" */
|
|
12
|
+
direction?: "left" | "right"
|
|
13
|
+
/** Durée d'un cycle complet, en secondes (défaut 20) */
|
|
14
|
+
duration?: number
|
|
15
|
+
/** Espace entre les deux copies (défaut 24px) */
|
|
16
|
+
gap?: string
|
|
17
|
+
/** Met en pause au survol */
|
|
18
|
+
pauseOnHover?: boolean
|
|
19
|
+
class?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
23
|
+
text: "",
|
|
24
|
+
direction: "left",
|
|
25
|
+
duration: 20,
|
|
26
|
+
gap: "24px",
|
|
27
|
+
pauseOnHover: false,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const marqueeStyle = computed<Record<string, string>>(() => ({
|
|
31
|
+
"--q-marquee-duration": `${props.duration}s`,
|
|
32
|
+
"--q-marquee-gap": props.gap,
|
|
33
|
+
}))
|
|
34
|
+
|
|
35
|
+
const marqueeClasses = computed(() =>
|
|
36
|
+
cn(
|
|
37
|
+
"q-marquee",
|
|
38
|
+
props.direction === "right" && "q-marquee--reverse",
|
|
39
|
+
props.pauseOnHover && "q-marquee--pause-on-hover",
|
|
40
|
+
props.class,
|
|
41
|
+
),
|
|
42
|
+
)
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<template>
|
|
46
|
+
<div class="q-marquee" :class="marqueeClasses" :style="marqueeStyle">
|
|
47
|
+
<div class="q-marquee__track">
|
|
48
|
+
<div class="q-marquee__copy">
|
|
49
|
+
<slot>{{ text }}</slot>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="q-marquee__copy" aria-hidden="true">
|
|
52
|
+
<slot>{{ text }}</slot>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</template>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// QMessageScroller — conteneur de la liste (équivalent MessageScroller).
|
|
3
|
+
// Enfant : QMessageScrollerViewport (+ QMessageScrollerButton en absolu).
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<div class="q-msg-scroller">
|
|
8
|
+
<slot />
|
|
9
|
+
</div>
|
|
10
|
+
</template>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// QMessageScrollerButton — bouton flottant de retour au live edge (équivalent MessageScrollerButton).
|
|
3
|
+
// Visible uniquement quand il reste du contenu à atteindre dans cette direction.
|
|
4
|
+
import { computed, inject } from "vue"
|
|
5
|
+
import { Icon } from "@iconify/vue"
|
|
6
|
+
import { icons } from "../lib/icons"
|
|
7
|
+
import { qMessageScrollerKey } from "./QMessageScrollerProvider.vue"
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
/** Direction du saut */
|
|
11
|
+
direction?: "start" | "end"
|
|
12
|
+
label?: string
|
|
13
|
+
behavior?: ScrollBehavior
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
17
|
+
direction: "end",
|
|
18
|
+
label: "Aller au plus récent",
|
|
19
|
+
behavior: "smooth",
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const ctx = inject(qMessageScrollerKey, null)
|
|
23
|
+
|
|
24
|
+
const active = computed(() => {
|
|
25
|
+
const s = ctx?.scrollable.value
|
|
26
|
+
if (!s) return false
|
|
27
|
+
return props.direction === "end" ? s.end : s.start
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const onClick = () => {
|
|
31
|
+
if (props.direction === "end") ctx?.scrollToEnd({ behavior: props.behavior })
|
|
32
|
+
else ctx?.scrollToStart({ behavior: props.behavior })
|
|
33
|
+
}
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<template>
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
39
|
+
class="q-msg-scroller__button"
|
|
40
|
+
:class="{ 'q-msg-scroller__button--active': active }"
|
|
41
|
+
:tabindex="active ? 0 : -1"
|
|
42
|
+
:aria-hidden="active ? undefined : 'true'"
|
|
43
|
+
:aria-label="label"
|
|
44
|
+
@click="onClick"
|
|
45
|
+
>
|
|
46
|
+
<Icon :icon="direction === 'end' ? icons.chevronDown : icons.chevronUp" aria-hidden="true" />
|
|
47
|
+
</button>
|
|
48
|
+
</template>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// QMessageScrollerContent — contenu du log (équivalent MessageScrollerContent).
|
|
3
|
+
// role="log" + aria-relevant="additions" : les lecteurs d'écran annoncent les nouveaux messages.
|
|
4
|
+
import { inject, ref, watch } from "vue"
|
|
5
|
+
import { qMessageScrollerKey } from "./QMessageScrollerProvider.vue"
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
/** État de streaming (aria-busy) */
|
|
9
|
+
ariaBusy?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const props = defineProps<Props>()
|
|
13
|
+
const ctx = inject(qMessageScrollerKey, null)
|
|
14
|
+
const el = ref<HTMLElement | null>(null)
|
|
15
|
+
|
|
16
|
+
watch(el, (v) => {
|
|
17
|
+
if (ctx) ctx.contentEl.value = v
|
|
18
|
+
}, { immediate: true })
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<template>
|
|
22
|
+
<div
|
|
23
|
+
ref="el"
|
|
24
|
+
class="q-msg-scroller__content"
|
|
25
|
+
role="log"
|
|
26
|
+
aria-relevant="additions"
|
|
27
|
+
:aria-busy="ariaBusy || undefined"
|
|
28
|
+
>
|
|
29
|
+
<slot />
|
|
30
|
+
</div>
|
|
31
|
+
</template>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// QMessageScrollerItem — ligne du transcript (équivalent MessageScrollerItem).
|
|
3
|
+
// message-id : ancrage/visibilité/sauts ; scroll-anchor : marque le début d'un tour.
|
|
4
|
+
import { inject, onBeforeUnmount, onMounted, ref } from "vue"
|
|
5
|
+
import { qMessageScrollerKey } from "./QMessageScrollerProvider.vue"
|
|
6
|
+
|
|
7
|
+
let itemSeq = 0
|
|
8
|
+
|
|
9
|
+
/** Presets d'animation d'entrée d'un message (portés depuis ui-thing) */
|
|
10
|
+
type QMessageAnimationPreset =
|
|
11
|
+
| "fade"
|
|
12
|
+
| "slide-up"
|
|
13
|
+
| "slide-side"
|
|
14
|
+
| "pop"
|
|
15
|
+
| "spring-bounce"
|
|
16
|
+
| "blur-fade"
|
|
17
|
+
| "scale-fade"
|
|
18
|
+
|
|
19
|
+
interface Props {
|
|
20
|
+
/** Identifiant stable (ancrage, visibilité, sauts) — auto-généré si absent */
|
|
21
|
+
messageId?: string
|
|
22
|
+
/** Marque cette ligne comme le début d'un tour (positionnée près du haut à l'ajout) */
|
|
23
|
+
scrollAnchor?: boolean
|
|
24
|
+
/** Animation d'entrée du message à son ajout (fade, slide-up, pop, spring-bounce…) */
|
|
25
|
+
animationPreset?: QMessageAnimationPreset
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
29
|
+
scrollAnchor: false,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const ctx = inject(qMessageScrollerKey, null)
|
|
33
|
+
const el = ref<HTMLElement | null>(null)
|
|
34
|
+
|
|
35
|
+
const resolvedId = props.messageId ?? `q-msg-item-${itemSeq++}`
|
|
36
|
+
|
|
37
|
+
onMounted(() => ctx?.registerItem({ id: resolvedId, el, anchor: props.scrollAnchor }))
|
|
38
|
+
onBeforeUnmount(() => ctx?.unregisterItem(resolvedId))
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<div
|
|
43
|
+
ref="el"
|
|
44
|
+
class="q-msg-scroller__item"
|
|
45
|
+
:class="animationPreset && `q-msg-anim-${animationPreset}`"
|
|
46
|
+
>
|
|
47
|
+
<slot />
|
|
48
|
+
</div>
|
|
49
|
+
</template>
|