@dnax/ui 0.0.34 → 0.0.36

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.
@@ -0,0 +1,132 @@
1
+ <script setup lang="ts">
2
+ // QBreadcrumbs — fil d'ariane : <q-breadcrumbs separator="›" gutter="12px" active-color="secondary" align="center" dense />
3
+ // Famille Quasar QBreadcrumbs + QBreadcrumbsEl, rendu façon shadcn-vue Breadcrumb
4
+ // (nav > ol > li, aria-current="page" sur la page courante).
5
+ //
6
+ // Le conteneur insère AUTOMATIQUEMENT un séparateur entre chaque miette du slot par
7
+ // défaut (en général des <q-breadcrumbs-el>) et marque la dernière miette comme la
8
+ // page courante (couleur active + graisse). Les vnodes bruts du slot sont réaffichés
9
+ // via le helper interne RenderNodes (intercaler les séparateurs = impossible en
10
+ // template pur).
11
+ import { Comment, Text, useSlots, type VNode } from "vue"
12
+ import { Icon } from "@iconify/vue"
13
+ import { cn } from "../lib/utils"
14
+ import { colorValue } from "../lib/colors"
15
+ import RenderNodes from "./internal/RenderNodes.vue"
16
+
17
+ /** "lucide:chevron-right", "mdi:chevron-right"… → séparateur rendu en icône Iconify */
18
+ const ICON_PREFIX_RE = /^[\w-]+:/
19
+ const isIconName = (name: string) => ICON_PREFIX_RE.test(name)
20
+
21
+ interface Props {
22
+ /** Séparateur entre les miettes : texte ("/", "›", "·"…) ou icône Iconify ("lucide:chevron-right") */
23
+ separator?: string
24
+ /** Couleur du séparateur (token ou hex) */
25
+ separatorColor?: string
26
+ /** Couleur de la dernière miette — la page courante (token ou hex, défaut primary) */
27
+ activeColor?: string
28
+ /** Couleur des miettes non courantes (token ou hex — défaut : texte adouci) */
29
+ color?: string
30
+ /** Espace entre les éléments (valeur CSS, défaut "8px") */
31
+ gutter?: string
32
+ /** Alignement du fil d'ariane */
33
+ align?: "left" | "center" | "right"
34
+ /** Taille réduite */
35
+ dense?: boolean
36
+ }
37
+
38
+ const props = withDefaults(defineProps<Props>(), {
39
+ separator: "/",
40
+ activeColor: "primary",
41
+ color: "",
42
+ gutter: "8px",
43
+ align: "left",
44
+ dense: false,
45
+ })
46
+
47
+ const emit = defineEmits<{
48
+ /**
49
+ * Émis quand l'utilisateur clique une miette qui n'est pas la page courante
50
+ * (la dernière) — payload : index de la miette + événement souris d'origine.
51
+ */
52
+ change: [index: number, event: MouseEvent]
53
+ }>()
54
+
55
+ const slots = useSlots()
56
+
57
+ interface CrumbRow {
58
+ crumb: VNode
59
+ isLast: boolean
60
+ }
61
+
62
+ /** Miettes du slot par défaut (commentaires et nœuds texte purs ignorés), avec le marqueur de dernière. */
63
+ const rows = (): CrumbRow[] => {
64
+ const list = (slots.default?.() ?? []).filter((v) => {
65
+ const t = v.type
66
+ return t !== Comment && t !== Text
67
+ })
68
+ return list.map((crumb, i) => ({ crumb, isLast: i === list.length - 1 }))
69
+ }
70
+
71
+ const containerStyle = (): Record<string, string> => {
72
+ const style: Record<string, string> = {}
73
+ if (props.separatorColor) style["--q-breadcrumbs-sep-color"] = colorValue(props.separatorColor)
74
+ if (props.activeColor) style["--q-breadcrumbs-active-color"] = colorValue(props.activeColor)
75
+ if (props.color) style["--q-breadcrumbs-color"] = colorValue(props.color)
76
+ if (props.gutter) style["--q-breadcrumbs-gutter"] = props.gutter
77
+ return style
78
+ }
79
+
80
+ const navClasses = (): string =>
81
+ cn(
82
+ "q-breadcrumbs",
83
+ props.align === "center" && "q-breadcrumbs--center",
84
+ props.align === "right" && "q-breadcrumbs--right",
85
+ props.dense && "q-breadcrumbs--dense",
86
+ )
87
+
88
+ // Délégation de clic sur le <nav> : le clic DOM remonte jusqu'ici même pour les
89
+ // miettes-lien (l'émission Vue de QBreadcrumbsEl ne « bulle » pas). On ignore la
90
+ // page courante (dernière miette) et les séparateurs.
91
+ const onNavClick = (e: MouseEvent) => {
92
+ const target = e.target
93
+ if (!(target instanceof Element)) return
94
+ const li = target.closest(".q-breadcrumbs__item")
95
+ if (!li || li.classList.contains("q-breadcrumbs__item--last")) return
96
+ const items = Array.from(li.parentElement?.children ?? []).filter((el) =>
97
+ el.classList.contains("q-breadcrumbs__item"),
98
+ )
99
+ const index = items.indexOf(li)
100
+ if (index >= 0) emit("change", index, e)
101
+ }
102
+ </script>
103
+
104
+ <template>
105
+ <nav
106
+ class="q-breadcrumbs"
107
+ :class="navClasses()"
108
+ :style="containerStyle()"
109
+ aria-label="Breadcrumb"
110
+ @click="onNavClick"
111
+ >
112
+ <ol class="q-breadcrumbs__list">
113
+ <template v-for="(row, i) in rows()" :key="i">
114
+ <li
115
+ class="q-breadcrumbs__item"
116
+ :class="{ 'q-breadcrumbs__item--last': row.isLast }"
117
+ :aria-current="row.isLast ? 'page' : undefined"
118
+ >
119
+ <render-nodes :nodes="row.crumb" />
120
+ </li>
121
+ <li
122
+ v-if="!row.isLast && separator !== ''"
123
+ class="q-breadcrumbs__separator"
124
+ aria-hidden="true"
125
+ >
126
+ <Icon v-if="isIconName(separator)" :icon="separator" />
127
+ <template v-else>{{ separator }}</template>
128
+ </li>
129
+ </template>
130
+ </ol>
131
+ </nav>
132
+ </template>
@@ -0,0 +1,84 @@
1
+ <script setup lang="ts">
2
+ // QBreadcrumbsEl — une miette du fil d'ariane : <q-breadcrumbs-el label="Home" icon="lucide:home" to="/" />
3
+ // Rendu comme lien routeur (<a>, prop `to`/`href`) ou simple texte (<span>) sinon —
4
+ // la dernière miette du <q-breadcrumbs> parent est la page courante (couleur active
5
+ // + aria-current gérés par le conteneur).
6
+ import { computed } from "vue"
7
+ import { Icon } from "@iconify/vue"
8
+ import { cn } from "../lib/utils"
9
+ import { useRouter } from "vue-router"
10
+ import type { RouteLocationRaw } from "vue-router"
11
+
12
+ interface Props {
13
+ /** Texte de la miette (sinon contenu du slot) */
14
+ label?: string | number
15
+ /** Icône Iconify affichée à gauche (ex. : "lucide:home") */
16
+ icon?: string
17
+ /** Destination routeur : rend un lien <a> qui navigue au clic (router.push/replace) */
18
+ to?: RouteLocationRaw
19
+ /** router.replace au lieu de router.push */
20
+ replace?: boolean
21
+ /** Lien natif (<a href>) — sans routeur */
22
+ href?: string
23
+ /** Cible du lien natif (ex. "_blank") */
24
+ target?: string
25
+ /** Désactive la miette (clics ignorés) */
26
+ disable?: boolean
27
+ }
28
+
29
+ const props = withDefaults(defineProps<Props>(), {
30
+ replace: false,
31
+ disable: false,
32
+ })
33
+
34
+ const emit = defineEmits<{ click: [event: MouseEvent] }>()
35
+
36
+ // Routeur optionnel : sans router installé, `to` dégrade en simple <span> (l'API
37
+ // `href` reste disponible pour les liens natifs).
38
+ let router: ReturnType<typeof useRouter> | undefined
39
+ try {
40
+ router = useRouter()
41
+ }
42
+ catch {
43
+ router = undefined
44
+ }
45
+
46
+ const resolvedHref = computed(() => {
47
+ if (props.href) return props.href
48
+ if (props.to && router) return router.resolve(props.to).href
49
+ return undefined
50
+ })
51
+
52
+ const tag = computed(() => (resolvedHref.value ? "a" : "span"))
53
+
54
+ const elClasses = computed(() =>
55
+ cn("q-breadcrumbs__el", props.disable && "q-breadcrumbs__el--disabled"),
56
+ )
57
+
58
+ const onClick = (e: MouseEvent) => {
59
+ if (props.disable) return
60
+ if (props.to && router && !e.defaultPrevented) {
61
+ e.preventDefault()
62
+ if (props.replace) router.replace(props.to)
63
+ else router.push(props.to)
64
+ }
65
+ emit("click", e)
66
+ }
67
+ </script>
68
+
69
+ <template>
70
+ <component
71
+ :is="tag"
72
+ :href="resolvedHref || undefined"
73
+ :target="tag === 'a' ? target || undefined : undefined"
74
+ :rel="tag === 'a' && target === '_blank' ? 'noopener' : undefined"
75
+ :tabindex="disable && tag === 'a' ? -1 : undefined"
76
+ :aria-disabled="disable || undefined"
77
+ :class="elClasses"
78
+ @click="onClick"
79
+ >
80
+ <Icon v-if="icon" :icon="icon" class="q-breadcrumbs__el-icon" aria-hidden="true" />
81
+ <span v-if="label !== undefined" class="q-breadcrumbs__el-label">{{ label }}</span>
82
+ <slot v-else />
83
+ </component>
84
+ </template>
@@ -7,8 +7,8 @@
7
7
  // contextes d'empilement/overflow (cellules sticky de table, scroll containers).
8
8
 
9
9
  export interface BtnAction {
10
- /** Libellé affiché dans le menu */
11
- label: string
10
+ /** Libellé affiché dans le menu (absent pour une entrée « séparateur seul ») */
11
+ label?: string
12
12
  /** Valeur émise par @select-action ; si absente, l'action elle-même est émise */
13
13
  value?: any
14
14
  /** Icône Iconify à gauche (ex. : "lucide:pencil") */
@@ -26,6 +26,20 @@ export interface BtnAction {
26
26
  /** Rappel local au clic (en plus de l'événement @select-action) */
27
27
  onClick?: (action: BtnAction) => void
28
28
  }
29
+
30
+ /**
31
+ * Placement du panneau autour du déclencheur (côté + alignement sur l'axe
32
+ * perpendiculaire) :
33
+ * - "bottom-start" / "bottom-end" : sous le bouton, bord gauche / droit aligné ;
34
+ * - "top-start" / "top-end" : au-dessus du bouton ;
35
+ * - "left-start"… / "right-start"… : sur le côté (start = haut, end = bas) ;
36
+ * - sans suffixe ("bottom", "top", "left", "right") : centré sur l'axe croisé.
37
+ */
38
+ export type DropdownPosition =
39
+ | "bottom-start" | "bottom-end" | "bottom"
40
+ | "top-start" | "top-end" | "top"
41
+ | "left-start" | "left-end" | "left"
42
+ | "right-start" | "right-end" | "right"
29
43
  </script>
30
44
 
31
45
  <script setup lang="ts">
@@ -48,8 +62,15 @@ interface Props {
48
62
  noCaret?: boolean
49
63
  /** Icône de la flèche déroulante */
50
64
  dropdownIcon?: string
51
- /** Alignement du menu : bord gauche du bouton ("left") ou bord droit ("right") */
65
+ /** Alignement du menu sous le bouton : "left" = bottom-start, "right" = bottom-end (alias de `position`) */
52
66
  align?: "left" | "right"
67
+ /**
68
+ * Placement du panneau autour du déclencheur — ex. "bottom-end" (défaut),
69
+ * "top-start", "right", "left-end"… (voir DropdownPosition). Prioritaire sur `align`.
70
+ */
71
+ position?: DropdownPosition
72
+ /** Distance entre le panneau et le déclencheur, en px (défaut : 4) */
73
+ offset?: number
53
74
  /** Largeur minimale du menu (ex. "200px") */
54
75
  menuWidth?: string
55
76
  // — API déclencheur (transmise à QBtn) —
@@ -64,6 +85,7 @@ interface Props {
64
85
  round?: boolean
65
86
  square?: boolean
66
87
  noCaps?: boolean
88
+ /** Pleine largeur : la racine `.q-btn-actions` s'étend sur 100 % (le déclencheur suit) */
67
89
  stretch?: boolean
68
90
  loading?: boolean
69
91
  disable?: boolean
@@ -75,6 +97,7 @@ const props = withDefaults(defineProps<Props>(), {
75
97
  noCaret: false,
76
98
  dropdownIcon: "lucide:chevron-down",
77
99
  align: "right",
100
+ offset: 4,
78
101
  menuWidth: "180px",
79
102
  size: "md",
80
103
  color: "primary",
@@ -126,17 +149,68 @@ const panelStyle = computed<Record<string, string>>(() => ({
126
149
  ...(roundedStyle.value ?? {}),
127
150
  }))
128
151
 
152
+ // `position` explicite, sinon alias `align` (compat) → bottom-start / bottom-end
153
+ const placement = computed<DropdownPosition>(
154
+ () => props.position ?? (props.align === "left" ? "bottom-start" : "bottom-end"),
155
+ )
156
+
129
157
  // ═════════ Positionnement (fixed, téléporté) ═════════
158
+ // Placement exprimé en « point d'ancrage » du panneau (top/left fixed) + un
159
+ // décalage par translation en % de la taille du panneau (−100% = bord opposé,
160
+ // −50% = centré) : aucun calcul de largeur/hauteur du panneau nécessaire.
130
161
  const placePanel = () => {
131
162
  const el = rootEl.value
132
163
  if (!el || typeof document === "undefined") return
133
164
  const rect = el.getBoundingClientRect()
134
- if (props.align === "left") {
135
- panelPos.value = { top: rect.bottom + 4, left: rect.left, transform: "none" }
165
+ const [main, alt] = placement.value.split("-") as ["bottom" | "top" | "left" | "right", "start" | "end" | undefined]
166
+ const off = props.offset
167
+ const midX = (rect.left + rect.right) / 2
168
+ const midY = (rect.top + rect.bottom) / 2
169
+
170
+ let top = 0
171
+ let left = 0
172
+ let tx = "0%"
173
+ let ty = "0%"
174
+
175
+ if (main === "bottom" || main === "top") {
176
+ // Axe horizontal : bord gauche (start), bord droit (end) ou centre
177
+ if (alt === "start") left = rect.left
178
+ else if (alt === "end") {
179
+ left = rect.right
180
+ tx = "-100%"
181
+ }
182
+ else {
183
+ left = midX
184
+ tx = "-50%"
185
+ }
186
+ if (main === "bottom") top = rect.bottom + off
187
+ else {
188
+ top = rect.top - off
189
+ ty = "-100%"
190
+ }
136
191
  }
137
192
  else {
138
- // bord droit du panneau aligné sur le bord droit du bouton
139
- panelPos.value = { top: rect.bottom + 4, left: rect.right, transform: "translateX(-100%)" }
193
+ // Axe vertical : haut (start), bas (end) ou centre
194
+ if (alt === "start") top = rect.top
195
+ else if (alt === "end") {
196
+ top = rect.bottom
197
+ ty = "-100%"
198
+ }
199
+ else {
200
+ top = midY
201
+ ty = "-50%"
202
+ }
203
+ if (main === "right") left = rect.right + off
204
+ else {
205
+ left = rect.left - off
206
+ tx = "-100%"
207
+ }
208
+ }
209
+
210
+ panelPos.value = {
211
+ top,
212
+ left,
213
+ transform: tx === "0%" && ty === "0%" ? "none" : `translate(${tx}, ${ty})`,
140
214
  }
141
215
  }
142
216
 
@@ -213,6 +287,21 @@ const isSeparatorOnly = (action: BtnAction): boolean =>
213
287
  const itemStyle = (action: BtnAction) =>
214
288
  action.color ? { color: colorValue(action.color) } : undefined
215
289
 
290
+ // — Liste aplatie du menu : un nœud stable PAR ligne (séparateur OU item), jamais
291
+ // deux frères conditionnels pour une même action. Évite la désynchronisation de
292
+ // l'arbre de blocs de Vue (crash `emitsOptions` null dans shouldUpdateComponent)
293
+ // quand le menu (Teleport + Transition) est patché avec un nombre variable d'enfants.
294
+ type MenuRow = { type: "separator"; key: string } | { type: "item"; key: string; action: BtnAction }
295
+
296
+ const menuRows = computed<MenuRow[]>(() => {
297
+ const rows: MenuRow[] = []
298
+ props.actions.forEach((action, i) => {
299
+ if (action.separator) rows.push({ type: "separator", key: `${i}-sep` })
300
+ if (!isSeparatorOnly(action)) rows.push({ type: "item", key: `${i}-item`, action })
301
+ })
302
+ return rows
303
+ })
304
+
216
305
  onMounted(() => {
217
306
  if (typeof document !== "undefined") document.addEventListener("mousedown", onDocMousedown)
218
307
  })
@@ -223,7 +312,12 @@ onBeforeUnmount(() => {
223
312
  </script>
224
313
 
225
314
  <template>
226
- <div ref="rootEl" class="q-btn-actions" :class="dark && 'dark'" @keydown="onKeydown">
315
+ <div
316
+ ref="rootEl"
317
+ class="q-btn-actions"
318
+ :class="[dark && 'dark', stretch && 'q-btn-actions--stretch']"
319
+ @keydown="onKeydown"
320
+ >
227
321
  <QBtn
228
322
  class="q-btn-actions__trigger"
229
323
  :class="triggerClasses"
@@ -267,24 +361,24 @@ onBeforeUnmount(() => {
267
361
  :style="panelStyle"
268
362
  @keydown="onKeydown"
269
363
  >
270
- <template v-for="(action, i) in actions" :key="i">
271
- <div v-if="action.separator" class="q-btn-actions__separator" role="separator" />
364
+ <template v-for="row in menuRows" :key="row.key">
365
+ <div v-if="row.type === 'separator'" class="q-btn-actions__separator" role="separator" />
272
366
  <button
273
- v-if="!isSeparatorOnly(action)"
367
+ v-else
274
368
  type="button"
275
369
  role="menuitem"
276
370
  class="q-btn-actions__item"
277
- :class="{ 'q-btn-actions__item--disabled': action.disable }"
278
- :disabled="action.disable"
279
- :style="itemStyle(action)"
280
- @click="onSelect(action)"
371
+ :class="{ 'q-btn-actions__item--disabled': row.action.disable }"
372
+ :disabled="row.action.disable"
373
+ :style="itemStyle(row.action)"
374
+ @click="onSelect(row.action)"
281
375
  >
282
- <Icon :icon="action.icon" v-if="action.icon" class="q-btn-actions__item-icon" aria-hidden="true" />
376
+ <Icon v-if="row.action.icon" :icon="row.action.icon" class="q-btn-actions__item-icon" aria-hidden="true" />
283
377
  <span class="q-btn-actions__item-text">
284
- <span class="q-btn-actions__item-label">{{ action.label }}</span>
285
- <span v-if="action.description" class="q-btn-actions__item-desc">{{ action.description }}</span>
378
+ <span class="q-btn-actions__item-label">{{ row.action.label }}</span>
379
+ <span v-if="row.action.description" class="q-btn-actions__item-desc">{{ row.action.description }}</span>
286
380
  </span>
287
- <Icon :icon="action.iconRight" v-if="action.iconRight" class="q-btn-actions__item-icon" aria-hidden="true" />
381
+ <Icon v-if="row.action.iconRight" :icon="row.action.iconRight" class="q-btn-actions__item-icon" aria-hidden="true" />
288
382
  </button>
289
383
  </template>
290
384
  </div>
@@ -0,0 +1,134 @@
1
+ <script lang="ts">
2
+ // QBtnDropdown — dropdown Quasar : <q-btn-dropdown label="…" :items="…" @select="…" />.
3
+ // Le déclencheur est un QBtn (+ flèche caret) ; le menu liste les `items`, chacun
4
+ // avec une icône GAUCHE (`leftIcon`) et une icône DROITE (`rightIcon`).
5
+ // Implémentation : réutilise le moteur de QBtnActions (panneau téléporté fixed,
6
+ // clavier, séparateurs, clic extérieur) — zéro duplication.
7
+
8
+ export interface DropdownItem {
9
+ /** Libellé affiché (absent pour une entrée « séparateur seul ») */
10
+ label?: string
11
+ /** Valeur émise par @select ; si absente, l'item lui-même est émis */
12
+ value?: any
13
+ /** Icône Iconify à GAUCHE du label (ex. "lucide:settings") */
14
+ leftIcon?: string
15
+ /** Icône Iconify à DROITE du label (ex. "lucide:chevron-right" pour un sous-menu) */
16
+ rightIcon?: string
17
+ /** Couleur du texte/icône (token — ex. "negative" — ou hex) */
18
+ color?: string
19
+ /** Sous-texte descriptif sous le label */
20
+ description?: string
21
+ /** Ligne de séparation affichée au-dessus de l'item */
22
+ separator?: boolean
23
+ /** Item désactivé */
24
+ disable?: boolean
25
+ /** Rappel local au clic (en plus de l'événement @select) */
26
+ onClick?: (item: DropdownItem) => void
27
+ }
28
+ </script>
29
+
30
+ <script setup lang="ts">
31
+ import { computed } from "vue"
32
+ import QBtnActions, { type DropdownPosition } from "./QBtnActions.vue"
33
+ import type { RadiusProp } from "../lib/useComponentProps"
34
+
35
+ interface Props {
36
+ /** Items du menu (icône gauche + icône droite par item) */
37
+ items?: DropdownItem[]
38
+ /** Libellé du bouton déclencheur (sinon icône seule) */
39
+ label?: string
40
+ /** Icône Iconify du déclencheur (défaut "lucide:ellipsis" si pas de label) */
41
+ icon?: string
42
+ /** Masque la flèche déroulante */
43
+ noCaret?: boolean
44
+ /** Icône de la flèche déroulante */
45
+ dropdownIcon?: string
46
+ /** Alignement du menu sous le bouton : "left" = bottom-start, "right" = bottom-end (alias de `position`) */
47
+ align?: "left" | "right"
48
+ /**
49
+ * Placement du panneau autour du déclencheur — ex. "bottom-end" (défaut),
50
+ * "top-start", "right", "left-end"… Prioritaire sur `align`.
51
+ */
52
+ position?: DropdownPosition
53
+ /** Distance entre le panneau et le déclencheur, en px (défaut : 4) */
54
+ offset?: number
55
+ /** Largeur minimale du menu (ex. "220px") */
56
+ menuWidth?: string
57
+ // — API déclencheur (transmise à QBtn) —
58
+ color?: string
59
+ textColor?: string
60
+ size?: string
61
+ radius?: RadiusProp
62
+ flat?: boolean
63
+ outline?: boolean
64
+ unelevated?: boolean
65
+ dense?: boolean
66
+ round?: boolean
67
+ square?: boolean
68
+ noCaps?: boolean
69
+ /** Pleine largeur : la racine `.q-btn-actions` s'étend sur 100 % (le déclencheur suit) */
70
+ stretch?: boolean
71
+ loading?: boolean
72
+ disable?: boolean
73
+ dark?: boolean
74
+ }
75
+
76
+ const props = withDefaults(defineProps<Props>(), {
77
+ items: () => [],
78
+ align: "right",
79
+ offset: 4,
80
+ menuWidth: "220px",
81
+ })
82
+
83
+ const emit = defineEmits<{
84
+ /** Item sélectionné — payload : `value` de l'item (ou l'item si pas de value) */
85
+ select: [value: any]
86
+ }>()
87
+
88
+ // leftIcon/rightIcon → icon/iconRight du moteur QBtnActions
89
+ const mappedActions = computed(() =>
90
+ (props.items ?? []).map((item) => ({
91
+ label: item.label,
92
+ value: item.value,
93
+ icon: item.leftIcon,
94
+ iconRight: item.rightIcon,
95
+ color: item.color,
96
+ description: item.description,
97
+ separator: item.separator,
98
+ disable: item.disable,
99
+ onClick: item.onClick ? () => item.onClick!(item) : undefined,
100
+ })),
101
+ )
102
+
103
+ const onSelectAction = (value: any) => emit("select", value)
104
+ </script>
105
+
106
+ <template>
107
+ <QBtnActions
108
+ :actions="mappedActions"
109
+ :label="label"
110
+ :icon="icon"
111
+ :no-caret="noCaret"
112
+ :dropdown-icon="dropdownIcon"
113
+ :align="align"
114
+ :position="position"
115
+ :offset="offset"
116
+ :menu-width="menuWidth"
117
+ :color="color"
118
+ :text-color="textColor"
119
+ :size="size"
120
+ :radius="radius"
121
+ :flat="flat"
122
+ :outline="outline"
123
+ :unelevated="unelevated"
124
+ :dense="dense"
125
+ :round="round"
126
+ :square="square"
127
+ :no-caps="noCaps"
128
+ :stretch="stretch"
129
+ :loading="loading"
130
+ :disable="disable"
131
+ :dark="dark"
132
+ @select-action="onSelectAction"
133
+ />
134
+ </template>