@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,27 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// QCardActions — API Quasar : <q-card-actions align="right" vertical>
|
|
3
|
+
// À utiliser dans le slot "actions" de QCard (ou n'importe où).
|
|
4
|
+
interface Props {
|
|
5
|
+
/** Alignement des actions */
|
|
6
|
+
align?: "left" | "center" | "right" | "between" | "around" | "evenly" | "stretch"
|
|
7
|
+
/** Empile verticalement */
|
|
8
|
+
vertical?: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
12
|
+
align: "stretch",
|
|
13
|
+
vertical: false,
|
|
14
|
+
})
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<div
|
|
19
|
+
class="q-card__actions"
|
|
20
|
+
:class="[
|
|
21
|
+
`q-card__actions--${props.align}`,
|
|
22
|
+
props.vertical && 'q-card__actions--vertical',
|
|
23
|
+
]"
|
|
24
|
+
>
|
|
25
|
+
<slot />
|
|
26
|
+
</div>
|
|
27
|
+
</template>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// QCardContent — bloc de contenu (équivalent QCardSection) : padding + alignement optionnel.
|
|
3
|
+
import { computed } from "vue"
|
|
4
|
+
import { cn } from "../lib/utils"
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
/** Espacement autour (défaut 20px) */
|
|
8
|
+
padding?: string
|
|
9
|
+
/** Alignement du texte */
|
|
10
|
+
align?: "left" | "center" | "right"
|
|
11
|
+
class?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
15
|
+
align: "left",
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const classes = computed(() => cn("q-card-content", `q-card-content--align-${props.align}`, props.class))
|
|
19
|
+
|
|
20
|
+
const style = computed<Record<string, string> | undefined>(() =>
|
|
21
|
+
props.padding ? { padding: props.padding } : undefined,
|
|
22
|
+
)
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<template>
|
|
26
|
+
<div class="q-card-content" :class="classes" :style="style">
|
|
27
|
+
<slot />
|
|
28
|
+
</div>
|
|
29
|
+
</template>
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// QCarousel — carrousel type Carousel shadcn-vue, construit sur Embla.
|
|
3
|
+
// <q-carousel :opts="…" :plugins="…" orientation="horizontal|vertical" @init-api="…">
|
|
4
|
+
// + QCarouselContent / QCarouselItem / QCarouselPrevious / QCarouselNext
|
|
5
|
+
import type { EmblaCarouselType, EmblaPluginType } from "embla-carousel"
|
|
6
|
+
import type { InjectionKey, Ref } from "vue"
|
|
7
|
+
|
|
8
|
+
export interface CarouselContext {
|
|
9
|
+
orientation: Readonly<Ref<"horizontal" | "vertical">>
|
|
10
|
+
emblaRef: Ref<HTMLElement | undefined>
|
|
11
|
+
api: Readonly<Ref<EmblaCarouselType | undefined>>
|
|
12
|
+
canScrollPrev: Readonly<Ref<boolean>>
|
|
13
|
+
canScrollNext: Readonly<Ref<boolean>>
|
|
14
|
+
selectedIndex: Readonly<Ref<number>>
|
|
15
|
+
scrollSnaps: Readonly<Ref<number[]>>
|
|
16
|
+
scrollPrev: () => void
|
|
17
|
+
scrollNext: () => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const qCarouselKey: InjectionKey<CarouselContext> = Symbol("q-carousel")
|
|
21
|
+
|
|
22
|
+
export type CarouselApi = EmblaCarouselType
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<script setup lang="ts">
|
|
26
|
+
import { computed, provide, ref, watch } from "vue"
|
|
27
|
+
import type { EmblaOptionsType } from "embla-carousel"
|
|
28
|
+
import useEmblaCarousel from "embla-carousel-vue"
|
|
29
|
+
|
|
30
|
+
interface Props {
|
|
31
|
+
/** Options Embla (align, loop, startIndex, …) */
|
|
32
|
+
opts?: EmblaOptionsType
|
|
33
|
+
/** Plugins Embla (ex. embla-carousel-autoplay) */
|
|
34
|
+
plugins?: EmblaPluginType[]
|
|
35
|
+
/** Orientation du carrousel */
|
|
36
|
+
orientation?: "horizontal" | "vertical"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
40
|
+
orientation: "horizontal",
|
|
41
|
+
plugins: () => [],
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const emit = defineEmits<{ initApi: [api: EmblaCarouselType] }>()
|
|
45
|
+
|
|
46
|
+
const orientation = computed(() => props.orientation)
|
|
47
|
+
|
|
48
|
+
const emblaOptions = computed<EmblaOptionsType>(() => ({
|
|
49
|
+
align: "start",
|
|
50
|
+
...props.opts,
|
|
51
|
+
axis: orientation.value === "vertical" ? "y" : "x",
|
|
52
|
+
}))
|
|
53
|
+
|
|
54
|
+
const emblaPlugins = computed(() => props.plugins)
|
|
55
|
+
|
|
56
|
+
const [emblaRef, emblaApi] = useEmblaCarousel(emblaOptions, emblaPlugins)
|
|
57
|
+
|
|
58
|
+
const canScrollPrev = ref(false)
|
|
59
|
+
const canScrollNext = ref(false)
|
|
60
|
+
const selectedIndex = ref(0)
|
|
61
|
+
const scrollSnaps = ref<number[]>([])
|
|
62
|
+
|
|
63
|
+
const onSelect = () => {
|
|
64
|
+
const api = emblaApi.value
|
|
65
|
+
if (!api) return
|
|
66
|
+
canScrollPrev.value = api.canScrollPrev()
|
|
67
|
+
canScrollNext.value = api.canScrollNext()
|
|
68
|
+
selectedIndex.value = api.selectedScrollSnap()
|
|
69
|
+
scrollSnaps.value = api.scrollSnapList()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
watch(
|
|
73
|
+
emblaApi,
|
|
74
|
+
(api) => {
|
|
75
|
+
if (!api) return
|
|
76
|
+
onSelect()
|
|
77
|
+
api.on("select", onSelect)
|
|
78
|
+
api.on("reInit", onSelect)
|
|
79
|
+
emit("initApi", api)
|
|
80
|
+
},
|
|
81
|
+
{ flush: "post" },
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
const scrollPrev = () => emblaApi.value?.scrollPrev()
|
|
85
|
+
const scrollNext = () => emblaApi.value?.scrollNext()
|
|
86
|
+
|
|
87
|
+
provide<CarouselContext>(qCarouselKey, {
|
|
88
|
+
orientation,
|
|
89
|
+
emblaRef,
|
|
90
|
+
api: emblaApi,
|
|
91
|
+
canScrollPrev,
|
|
92
|
+
canScrollNext,
|
|
93
|
+
selectedIndex,
|
|
94
|
+
scrollSnaps,
|
|
95
|
+
scrollPrev,
|
|
96
|
+
scrollNext,
|
|
97
|
+
})
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
<template>
|
|
101
|
+
<div
|
|
102
|
+
class="q-carousel"
|
|
103
|
+
:class="orientation === 'vertical' && 'q-carousel--vertical'"
|
|
104
|
+
>
|
|
105
|
+
<slot
|
|
106
|
+
:api="emblaApi"
|
|
107
|
+
:can-scroll-prev="canScrollPrev"
|
|
108
|
+
:can-scroll-next="canScrollNext"
|
|
109
|
+
:scroll-prev="scrollPrev"
|
|
110
|
+
:scroll-next="scrollNext"
|
|
111
|
+
:selected-index="selectedIndex"
|
|
112
|
+
:scroll-snaps="scrollSnaps"
|
|
113
|
+
/>
|
|
114
|
+
</div>
|
|
115
|
+
</template>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// QCarouselContent — le viewport scrollable + la piste flex (équivalent CarouselContent).
|
|
3
|
+
// L'emblaRef est appliqué sur le viewport (racine de mesure Embla).
|
|
4
|
+
import { inject } from "vue"
|
|
5
|
+
import { qCarouselKey } from "./QCarousel.vue"
|
|
6
|
+
|
|
7
|
+
const carousel = inject(qCarouselKey, null)
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<div
|
|
12
|
+
:ref="carousel?.emblaRef"
|
|
13
|
+
class="q-carousel__viewport"
|
|
14
|
+
:class="{ 'q-carousel__viewport--vertical': carousel?.orientation.value === 'vertical' }"
|
|
15
|
+
>
|
|
16
|
+
<div
|
|
17
|
+
class="q-carousel__container"
|
|
18
|
+
:class="{ 'q-carousel__container--vertical': carousel?.orientation.value === 'vertical' }"
|
|
19
|
+
>
|
|
20
|
+
<slot />
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// QCarouselItem — slide du carrousel (équivalent CarouselItem).
|
|
3
|
+
// basis-full par défaut ; passer une classe (basis-1/2, md:basis-1/3…) pour la taille.
|
|
4
|
+
import { computed } from "vue"
|
|
5
|
+
import { cn } from "../lib/utils"
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
class?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const props = defineProps<Props>()
|
|
12
|
+
|
|
13
|
+
const classes = computed(() => cn("q-carousel__item", props.class))
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<div class="q-carousel__item" :class="classes">
|
|
18
|
+
<slot />
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// QCarouselPrevious / QCarouselNext — boutons de navigation (équivalent CarouselPrevious/Next).
|
|
3
|
+
import { computed, inject } from "vue"
|
|
4
|
+
import { Icon } from "@iconify/vue"
|
|
5
|
+
import { icons } from "../lib/icons"
|
|
6
|
+
import { qCarouselKey } from "./QCarousel.vue"
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
/** "prev" ou "next" */
|
|
10
|
+
direction: "prev" | "next"
|
|
11
|
+
label?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
15
|
+
label: "",
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const carousel = inject(qCarouselKey, null)
|
|
19
|
+
|
|
20
|
+
const isVertical = computed(() => carousel?.orientation.value === "vertical")
|
|
21
|
+
const disabled = computed(() =>
|
|
22
|
+
props.direction === "prev" ? !carousel?.canScrollPrev.value : !carousel?.canScrollNext.value,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
const onClick = () => {
|
|
26
|
+
if (props.direction === "prev") carousel?.scrollPrev()
|
|
27
|
+
else carousel?.scrollNext()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const icon = computed(() => {
|
|
31
|
+
if (props.direction === "prev") return isVertical.value ? icons.chevronUp : icons.chevronLeft
|
|
32
|
+
return isVertical.value ? icons.chevronDown : icons.chevronRight
|
|
33
|
+
})
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<template>
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
39
|
+
class="q-carousel__nav"
|
|
40
|
+
:class="[
|
|
41
|
+
props.direction === 'prev' ? 'q-carousel__nav--prev' : 'q-carousel__nav--next',
|
|
42
|
+
isVertical && 'q-carousel__nav--vertical',
|
|
43
|
+
disabled && 'q-carousel__nav--disabled',
|
|
44
|
+
]"
|
|
45
|
+
:disabled="disabled"
|
|
46
|
+
:aria-label="label || (direction === 'prev' ? 'Slide précédente' : 'Slide suivante')"
|
|
47
|
+
@click="onClick"
|
|
48
|
+
>
|
|
49
|
+
<Icon :icon="icon" aria-hidden="true" />
|
|
50
|
+
</button>
|
|
51
|
+
</template>
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// QCheckbox — API Quasar : <q-checkbox v-model="val" label="Option" color="secondary" dense left-label keep-color />
|
|
3
|
+
// Modèle : booléen | indéterminé (indeterminateValue) | tableau (avec prop val).
|
|
4
|
+
import { computed, useSlots } from "vue"
|
|
5
|
+
import { Icon } from "@iconify/vue"
|
|
6
|
+
import { icons } from "../lib/icons"
|
|
7
|
+
import { cn } from "../lib/utils"
|
|
8
|
+
import { colorValue } from "../lib/colors"
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
modelValue?: unknown
|
|
12
|
+
/** Valeur ajoutée/retirée quand le modèle est un tableau */
|
|
13
|
+
val?: unknown
|
|
14
|
+
trueValue?: unknown
|
|
15
|
+
falseValue?: unknown
|
|
16
|
+
/** Valeur représentant l'état indéterminé (défaut : null) */
|
|
17
|
+
indeterminateValue?: unknown
|
|
18
|
+
/** Icônes Iconify des états (défauts : lucide:check, lucide:minus) */
|
|
19
|
+
checkedIcon?: string
|
|
20
|
+
uncheckedIcon?: string
|
|
21
|
+
indeterminateIcon?: string
|
|
22
|
+
/** Couleur (token ou hex) */
|
|
23
|
+
color?: string
|
|
24
|
+
/** Applique la couleur aussi à l'état décoché */
|
|
25
|
+
keepColor?: boolean
|
|
26
|
+
dense?: boolean
|
|
27
|
+
dark?: boolean
|
|
28
|
+
disable?: boolean
|
|
29
|
+
readonly?: boolean
|
|
30
|
+
label?: string
|
|
31
|
+
/** Label à gauche du contrôle */
|
|
32
|
+
leftLabel?: boolean
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
36
|
+
color: "primary",
|
|
37
|
+
keepColor: false,
|
|
38
|
+
dense: false,
|
|
39
|
+
dark: false,
|
|
40
|
+
disable: false,
|
|
41
|
+
readonly: false,
|
|
42
|
+
leftLabel: false,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// Defaults manuels (withDefaults ne gère pas les defaults sur props typées unknown)
|
|
46
|
+
const trueValue = computed(() => props.trueValue ?? true)
|
|
47
|
+
const falseValue = computed(() => props.falseValue ?? false)
|
|
48
|
+
const indeterminateValue = computed(() => props.indeterminateValue ?? null)
|
|
49
|
+
|
|
50
|
+
const emit = defineEmits<{
|
|
51
|
+
"update:modelValue": [value: unknown]
|
|
52
|
+
focus: [event: FocusEvent]
|
|
53
|
+
blur: [event: FocusEvent]
|
|
54
|
+
keyup: [event: KeyboardEvent]
|
|
55
|
+
keydown: [event: KeyboardEvent]
|
|
56
|
+
}>()
|
|
57
|
+
|
|
58
|
+
const isArrayModel = computed(() => Array.isArray(props.modelValue))
|
|
59
|
+
|
|
60
|
+
const isChecked = computed(() =>
|
|
61
|
+
isArrayModel.value
|
|
62
|
+
? (props.modelValue as unknown[]).includes(props.val)
|
|
63
|
+
: props.modelValue === trueValue.value,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
const isIndeterminate = computed(
|
|
67
|
+
() => !isArrayModel.value && props.modelValue === indeterminateValue.value,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
const isDisabled = computed(() => props.disable || props.readonly)
|
|
71
|
+
|
|
72
|
+
const toggle = () => {
|
|
73
|
+
if (isDisabled.value) return
|
|
74
|
+
if (isArrayModel.value) {
|
|
75
|
+
const list = props.modelValue as unknown[]
|
|
76
|
+
emit(
|
|
77
|
+
"update:modelValue",
|
|
78
|
+
isChecked.value ? list.filter((v) => v !== props.val) : [...list, props.val],
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// indéterminé → coché (comportement Quasar)
|
|
83
|
+
emit("update:modelValue", isChecked.value ? falseValue.value : trueValue.value)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const boxStyle = computed<Record<string, string>>(() => {
|
|
88
|
+
const style: Record<string, string> = {}
|
|
89
|
+
const color = colorValue(props.color)
|
|
90
|
+
if (isChecked.value || isIndeterminate.value) {
|
|
91
|
+
style.backgroundColor = color
|
|
92
|
+
style.borderColor = color // pas de contour noir autour du fond coloré
|
|
93
|
+
}
|
|
94
|
+
else if (props.keepColor) style.borderColor = color
|
|
95
|
+
return style
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
const rootClasses = computed(() =>
|
|
99
|
+
cn(
|
|
100
|
+
"q-checkbox",
|
|
101
|
+
props.dense && "q-checkbox--dense",
|
|
102
|
+
props.dark && "q-checkbox--dark",
|
|
103
|
+
props.disable && "q-checkbox--disabled",
|
|
104
|
+
),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
const slots = useSlots()
|
|
108
|
+
|
|
109
|
+
const ariaChecked = computed(() =>
|
|
110
|
+
isIndeterminate.value ? "mixed" : isChecked.value ? "true" : "false",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
const hasLabel = computed(() => props.label !== undefined || !!slots.default)
|
|
114
|
+
</script>
|
|
115
|
+
|
|
116
|
+
<template>
|
|
117
|
+
<button
|
|
118
|
+
type="button"
|
|
119
|
+
role="checkbox"
|
|
120
|
+
:aria-checked="ariaChecked"
|
|
121
|
+
:disabled="isDisabled"
|
|
122
|
+
class="q-checkbox"
|
|
123
|
+
:class="rootClasses"
|
|
124
|
+
@click="toggle"
|
|
125
|
+
@focus="(e) => emit('focus', e)"
|
|
126
|
+
@blur="(e) => emit('blur', e)"
|
|
127
|
+
@keyup="(e) => emit('keyup', e)"
|
|
128
|
+
@keydown="(e) => emit('keydown', e)"
|
|
129
|
+
>
|
|
130
|
+
<span v-if="leftLabel && hasLabel" class="q-checkbox__label">
|
|
131
|
+
<slot>{{ label }}</slot>
|
|
132
|
+
</span>
|
|
133
|
+
<span class="q-checkbox__box" :style="boxStyle" aria-hidden="true">
|
|
134
|
+
<Icon
|
|
135
|
+
:icon="indeterminateIcon || icons.minus"
|
|
136
|
+
v-if="isIndeterminate"
|
|
137
|
+
class="q-checkbox__icon"
|
|
138
|
+
/>
|
|
139
|
+
<Icon :icon="checkedIcon || icons.check" v-else-if="isChecked" class="q-checkbox__icon" />
|
|
140
|
+
<Icon :icon="uncheckedIcon" v-else-if="uncheckedIcon" class="q-checkbox__icon" />
|
|
141
|
+
</span>
|
|
142
|
+
<span v-if="!leftLabel && hasLabel" class="q-checkbox__label">
|
|
143
|
+
<slot>{{ label }}</slot>
|
|
144
|
+
</span>
|
|
145
|
+
</button>
|
|
146
|
+
</template>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// QChip — API Quasar : <q-chip label="Chip" icon="…" removable color="secondary" outline square dense>
|
|
3
|
+
// removable → bouton de retrait qui émet "remove"
|
|
4
|
+
import { computed } from "vue"
|
|
5
|
+
import { Icon } from "@iconify/vue"
|
|
6
|
+
import { icons } from "../lib/icons"
|
|
7
|
+
import { cn } from "../lib/utils"
|
|
8
|
+
import { colorValue, foregroundFor } from "../lib/colors"
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
/** Texte */
|
|
12
|
+
label?: string
|
|
13
|
+
/** Icône Iconify à gauche (ex. : "lucide:star") */
|
|
14
|
+
icon?: string
|
|
15
|
+
/** Icône Iconify à droite */
|
|
16
|
+
iconRight?: string
|
|
17
|
+
/** Icône Iconify du bouton de retrait (défaut : lucide:x) */
|
|
18
|
+
iconRemove?: string
|
|
19
|
+
/** Affiche le bouton de retrait */
|
|
20
|
+
removable?: boolean
|
|
21
|
+
/** Contour seul */
|
|
22
|
+
outline?: boolean
|
|
23
|
+
/** Coins droits */
|
|
24
|
+
square?: boolean
|
|
25
|
+
/** Hauteur réduite */
|
|
26
|
+
dense?: boolean
|
|
27
|
+
/** Couleur de fond (token ou hex) */
|
|
28
|
+
color?: string
|
|
29
|
+
/** Couleur du texte (token ou hex) */
|
|
30
|
+
textColor?: string
|
|
31
|
+
/** Désactivé */
|
|
32
|
+
disable?: boolean
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
36
|
+
color: "primary",
|
|
37
|
+
textColor: "",
|
|
38
|
+
removable: false,
|
|
39
|
+
outline: false,
|
|
40
|
+
square: false,
|
|
41
|
+
dense: false,
|
|
42
|
+
disable: false,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const emit = defineEmits<{ remove: []; click: [event: MouseEvent] }>()
|
|
46
|
+
|
|
47
|
+
const chipStyle = computed<Record<string, string>>(() => {
|
|
48
|
+
const style: Record<string, string> = {}
|
|
49
|
+
style["--q-chip-bg"] = colorValue(props.color)
|
|
50
|
+
style["--q-chip-fg"] = props.textColor ? colorValue(props.textColor) : foregroundFor(props.color)
|
|
51
|
+
return style
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const chipClasses = computed(() =>
|
|
55
|
+
cn(
|
|
56
|
+
"q-chip",
|
|
57
|
+
props.outline && "q-chip--outline",
|
|
58
|
+
props.square && "q-chip--square",
|
|
59
|
+
props.dense && "q-chip--dense",
|
|
60
|
+
props.disable && "q-chip--disabled",
|
|
61
|
+
),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
const onClick = (e: MouseEvent) => {
|
|
65
|
+
if (props.disable) return
|
|
66
|
+
emit("click", e)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const onRemove = () => {
|
|
70
|
+
if (props.disable) return
|
|
71
|
+
emit("remove")
|
|
72
|
+
}
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<template>
|
|
76
|
+
<div class="q-chip" :class="chipClasses" :style="chipStyle" @click="onClick">
|
|
77
|
+
<Icon :icon="icon" v-if="icon" class="q-chip__icon" aria-hidden="true" />
|
|
78
|
+
<span v-if="label" class="q-chip__label">{{ label }}</span>
|
|
79
|
+
<slot v-else />
|
|
80
|
+
<Icon :icon="iconRight" v-if="iconRight" class="q-chip__icon q-chip__icon--right" aria-hidden="true" />
|
|
81
|
+
<button
|
|
82
|
+
v-if="removable"
|
|
83
|
+
class="q-chip__remove"
|
|
84
|
+
type="button"
|
|
85
|
+
aria-label="Retirer"
|
|
86
|
+
@click.stop="onRemove"
|
|
87
|
+
>
|
|
88
|
+
<Icon :icon="iconRemove || icons.x" />
|
|
89
|
+
</button>
|
|
90
|
+
</div>
|
|
91
|
+
</template>
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// QCircularProgress — anneau de progression : <q-circular-progress :value="0.7" size="64px" show-value />
|
|
3
|
+
// SVG stroke-dasharray ; valeur centrée optionnelle (prop, slot ou #value).
|
|
4
|
+
import { computed } from "vue"
|
|
5
|
+
import { colorValue } from "../lib/colors"
|
|
6
|
+
|
|
7
|
+
const SIZE_MAP: Record<string, string> = { xs: "32px", sm: "48px", md: "64px", lg: "96px", xl: "128px" }
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
/** Valeur (entre min et max, défaut 0-1) */
|
|
11
|
+
value?: number
|
|
12
|
+
min?: number
|
|
13
|
+
max?: number
|
|
14
|
+
/** Diamètre : xs→xl ou valeur CSS */
|
|
15
|
+
size?: string
|
|
16
|
+
/** Épaisseur de l'anneau (px) */
|
|
17
|
+
thickness?: number
|
|
18
|
+
/** Couleur de l'anneau (token ou hex) */
|
|
19
|
+
color?: string
|
|
20
|
+
/** Couleur de la piste (défaut : gris clair) */
|
|
21
|
+
trackColor?: string
|
|
22
|
+
/** Affiche la valeur centrée */
|
|
23
|
+
showValue?: boolean
|
|
24
|
+
/** Sens inverse */
|
|
25
|
+
reverse?: boolean
|
|
26
|
+
/** Mode indéterminé (rotation) */
|
|
27
|
+
indeterminate?: boolean
|
|
28
|
+
dark?: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
32
|
+
value: 0,
|
|
33
|
+
min: 0,
|
|
34
|
+
max: 1,
|
|
35
|
+
size: "md",
|
|
36
|
+
thickness: 6,
|
|
37
|
+
color: "primary",
|
|
38
|
+
trackColor: "",
|
|
39
|
+
showValue: false,
|
|
40
|
+
reverse: false,
|
|
41
|
+
indeterminate: false,
|
|
42
|
+
dark: false,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const ratio = computed(() => {
|
|
46
|
+
const span = props.max - props.min
|
|
47
|
+
if (span <= 0) return 0
|
|
48
|
+
return Math.min(1, Math.max(0, (props.value - props.min) / span))
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const diameter = computed(() => SIZE_MAP[props.size] ?? props.size)
|
|
52
|
+
|
|
53
|
+
// SVG : r = (diamètre - épaisseur) / 2 ; circonférence = 2πr
|
|
54
|
+
const r = computed(() => {
|
|
55
|
+
const d = parseFloat(diameter.value) || 64
|
|
56
|
+
return (d - props.thickness) / 2
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const circumference = computed(() => 2 * Math.PI * r.value)
|
|
60
|
+
|
|
61
|
+
const dashOffset = computed(() => circumference.value * (1 - ratio.value))
|
|
62
|
+
|
|
63
|
+
const stroke = computed(() => colorValue(props.color))
|
|
64
|
+
|
|
65
|
+
const rootStyle = computed<Record<string, string>>(() => ({
|
|
66
|
+
width: diameter.value,
|
|
67
|
+
height: diameter.value,
|
|
68
|
+
}))
|
|
69
|
+
|
|
70
|
+
const valueStyle = computed<Record<string, string>>(() => ({
|
|
71
|
+
color: props.color ? colorValue(props.color) : "",
|
|
72
|
+
fontSize: `calc(${diameter.value} * 0.25)`,
|
|
73
|
+
}))
|
|
74
|
+
|
|
75
|
+
const displayValue = computed(() => Math.round(ratio.value * 100))
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
<template>
|
|
79
|
+
<div
|
|
80
|
+
class="q-circular-progress"
|
|
81
|
+
:class="{
|
|
82
|
+
'q-circular-progress--dark': dark,
|
|
83
|
+
'q-circular-progress--indeterminate': indeterminate,
|
|
84
|
+
}"
|
|
85
|
+
:style="rootStyle"
|
|
86
|
+
role="progressbar"
|
|
87
|
+
:aria-valuenow="value"
|
|
88
|
+
:aria-valuemin="min"
|
|
89
|
+
:aria-valuemax="max"
|
|
90
|
+
>
|
|
91
|
+
<svg class="q-circular-progress__svg" :width="diameter" :height="diameter" viewBox="0 0 100 100">
|
|
92
|
+
<!-- piste -->
|
|
93
|
+
<circle
|
|
94
|
+
cx="50"
|
|
95
|
+
cy="50"
|
|
96
|
+
:r="50 - props.thickness"
|
|
97
|
+
class="q-circular-progress__track"
|
|
98
|
+
:class="{ 'q-circular-progress__track--dark': dark }"
|
|
99
|
+
fill="none"
|
|
100
|
+
:stroke="trackColor ? colorValue(trackColor) : undefined"
|
|
101
|
+
:stroke-width="thickness"
|
|
102
|
+
/>
|
|
103
|
+
<!-- anneau de progression -->
|
|
104
|
+
<circle
|
|
105
|
+
cx="50"
|
|
106
|
+
cy="50"
|
|
107
|
+
:r="50 - props.thickness"
|
|
108
|
+
class="q-circular-progress__bar"
|
|
109
|
+
fill="none"
|
|
110
|
+
:stroke="stroke"
|
|
111
|
+
:stroke-width="thickness"
|
|
112
|
+
stroke-linecap="round"
|
|
113
|
+
:stroke-dasharray="circumference"
|
|
114
|
+
:stroke-dashoffset="reverse ? -dashOffset : dashOffset"
|
|
115
|
+
:transform="`rotate(-90 50 50)`"
|
|
116
|
+
/>
|
|
117
|
+
</svg>
|
|
118
|
+
<div v-if="showValue && !indeterminate" class="q-circular-progress__value" :style="valueStyle">
|
|
119
|
+
<slot name="value">{{ displayValue }}%</slot>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</template>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// QCol — cellule de la grille : <q-col :span="6" :span-md="4" :offset="1">
|
|
3
|
+
// Responsive : span-sm/md/lg/xl et offset-* écrasent la valeur de base au breakpoint.
|
|
4
|
+
import { computed } from "vue"
|
|
5
|
+
import { useGridBreakpoints } from "../lib/breakpoints"
|
|
6
|
+
|
|
7
|
+
type ColSpan = number | "auto"
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
/** Colonnes occupées (1..12, ou "auto") — valeur de base (mobile) */
|
|
11
|
+
span?: ColSpan
|
|
12
|
+
/** Décalage en colonnes */
|
|
13
|
+
offset?: number
|
|
14
|
+
spanSm?: ColSpan
|
|
15
|
+
spanMd?: ColSpan
|
|
16
|
+
spanLg?: ColSpan
|
|
17
|
+
spanXl?: ColSpan
|
|
18
|
+
offsetSm?: number
|
|
19
|
+
offsetMd?: number
|
|
20
|
+
offsetLg?: number
|
|
21
|
+
offsetXl?: number
|
|
22
|
+
alignSelf?: "start" | "center" | "end" | "stretch"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
26
|
+
span: "auto",
|
|
27
|
+
alignSelf: "stretch",
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const bp = useGridBreakpoints()
|
|
31
|
+
|
|
32
|
+
const classes = computed(() => ({
|
|
33
|
+
...(props.span !== "auto" ? { [`q-col--span-${props.span}`]: true } : {}),
|
|
34
|
+
...(props.offset ? { [`q-col--offset-${props.offset}`]: true } : {}),
|
|
35
|
+
...(bp.value.sm && props.spanSm !== undefined && props.spanSm !== "auto" ? { [`q-col--span-sm-${props.spanSm}`]: true } : {}),
|
|
36
|
+
...(bp.value.sm && props.offsetSm ? { [`q-col--offset-sm-${props.offsetSm}`]: true } : {}),
|
|
37
|
+
...(bp.value.md && props.spanMd !== undefined && props.spanMd !== "auto" ? { [`q-col--span-md-${props.spanMd}`]: true } : {}),
|
|
38
|
+
...(bp.value.md && props.offsetMd ? { [`q-col--offset-md-${props.offsetMd}`]: true } : {}),
|
|
39
|
+
...(bp.value.lg && props.spanLg !== undefined && props.spanLg !== "auto" ? { [`q-col--span-lg-${props.spanLg}`]: true } : {}),
|
|
40
|
+
...(bp.value.lg && props.offsetLg ? { [`q-col--offset-lg-${props.offsetLg}`]: true } : {}),
|
|
41
|
+
...(bp.value.xl && props.spanXl !== undefined && props.spanXl !== "auto" ? { [`q-col--span-xl-${props.spanXl}`]: true } : {}),
|
|
42
|
+
...(bp.value.xl && props.offsetXl ? { [`q-col--offset-xl-${props.offsetXl}`]: true } : {}),
|
|
43
|
+
}))
|
|
44
|
+
|
|
45
|
+
const style = computed(() => (props.alignSelf !== "stretch" ? { alignSelf: props.alignSelf } : undefined))
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<template>
|
|
49
|
+
<div class="q-col" :class="classes" :style="style">
|
|
50
|
+
<slot />
|
|
51
|
+
</div>
|
|
52
|
+
</template>
|