@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,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>