@imaginario27/air-ui-ds 1.0.0

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 (220) hide show
  1. package/assets/css/defaults.css +55 -0
  2. package/assets/css/main.css +238 -0
  3. package/assets/css/theme/colors.css +106 -0
  4. package/assets/css/theme/primitives.css +105 -0
  5. package/assets/css/theme/ui-theme.css +454 -0
  6. package/assets/images/placeholders/missing-image-placeholder.png +0 -0
  7. package/components/accordions/Accordion.vue +31 -0
  8. package/components/accordions/AccordionGroup.vue +78 -0
  9. package/components/accordions/AccordionItem.vue +39 -0
  10. package/components/action-panels/ActionPanel.vue +49 -0
  11. package/components/alerts/Alert.vue +159 -0
  12. package/components/avatars/Avatar.vue +152 -0
  13. package/components/avatars/AvatarStack.vue +97 -0
  14. package/components/avatars/AvatarStackCounter.vue +74 -0
  15. package/components/badges/Badge.vue +221 -0
  16. package/components/badges/BadgeStack.vue +110 -0
  17. package/components/badges/IconBadge.vue +57 -0
  18. package/components/badges/IconTextBadge.vue +50 -0
  19. package/components/breadcrumbs/Breadcrumbs.vue +54 -0
  20. package/components/buttons/ActionButton.vue +395 -0
  21. package/components/buttons/ActionIconButton.vue +283 -0
  22. package/components/buttons/AlertButton.vue +125 -0
  23. package/components/buttons/AlertIconButton.vue +105 -0
  24. package/components/buttons/PaginationButton.vue +45 -0
  25. package/components/buttons/options/OptionButton.vue +61 -0
  26. package/components/buttons/options/OptionButtonGroup.vue +155 -0
  27. package/components/buttons/options/OptionButtonSlider.vue +154 -0
  28. package/components/buttons/toggle/ToggleButton.vue +142 -0
  29. package/components/buttons/toggle/ToggleButtonGroup.vue +73 -0
  30. package/components/cards/Card.vue +33 -0
  31. package/components/cards/CardActions.vue +5 -0
  32. package/components/cards/CardBody.vue +5 -0
  33. package/components/cards/CardFooter.vue +20 -0
  34. package/components/cards/CardHeader.vue +5 -0
  35. package/components/cards/CardTitle.vue +13 -0
  36. package/components/cards/specific/ContactDetailsCard.vue +47 -0
  37. package/components/cards/specific/FeatureCard.vue +59 -0
  38. package/components/cards/specific/HelpTopicCard.vue +62 -0
  39. package/components/cards/specific/MetricCard.vue +42 -0
  40. package/components/cards/specific/TestimonialCard.vue +57 -0
  41. package/components/cards/specific/subscription/CurrentActiveSubscriptionCard.vue +105 -0
  42. package/components/cards/specific/subscription/SubscriptionPlanCard.vue +178 -0
  43. package/components/cards/specific/subscription/UniqueSubscriptionPlanCard.vue +106 -0
  44. package/components/collapsibles/Collapsible.vue +33 -0
  45. package/components/content/ContentItem.vue +144 -0
  46. package/components/content/ContentItemImage.vue +125 -0
  47. package/components/dividers/Divider.vue +35 -0
  48. package/components/dividers/TextLineDivider.vue +58 -0
  49. package/components/dropdowns/DropdownMenu.vue +207 -0
  50. package/components/dropdowns/DropdownMenuActions.vue +11 -0
  51. package/components/dropdowns/DropdownMenuItem.vue +240 -0
  52. package/components/dropdowns/DropdownSelect.vue +469 -0
  53. package/components/dropdowns/DropdownSelectItem.vue +182 -0
  54. package/components/empty-states/EmptyState.vue +170 -0
  55. package/components/features/Feature.vue +77 -0
  56. package/components/forms/DataDetails.vue +7 -0
  57. package/components/forms/DataDetailsActions.vue +23 -0
  58. package/components/forms/DataDetailsFieldGroup.vue +35 -0
  59. package/components/forms/DataDetailsRow.vue +22 -0
  60. package/components/forms/Form.vue +25 -0
  61. package/components/forms/FormActions.vue +23 -0
  62. package/components/forms/FormFieldGroup.vue +35 -0
  63. package/components/forms/FormRow.vue +22 -0
  64. package/components/forms/fields/ButtonField.vue +119 -0
  65. package/components/forms/fields/CheckboxField.vue +205 -0
  66. package/components/forms/fields/DataField.vue +99 -0
  67. package/components/forms/fields/FileUploadField.vue +326 -0
  68. package/components/forms/fields/InputField.vue +371 -0
  69. package/components/forms/fields/OptionButtonsGroupField.vue +120 -0
  70. package/components/forms/fields/RepeaterField.vue +109 -0
  71. package/components/forms/fields/SearchField.vue +184 -0
  72. package/components/forms/fields/SelectField.vue +233 -0
  73. package/components/forms/fields/SliderField.vue +759 -0
  74. package/components/forms/fields/SwitchField.vue +257 -0
  75. package/components/forms/fields/TextareaField.vue +205 -0
  76. package/components/forms/fields/ToggleButtonsGroupField.vue +65 -0
  77. package/components/forms/fields/radio/RadioButtonField.vue +238 -0
  78. package/components/forms/fields/radio/RadioField.vue +157 -0
  79. package/components/forms/fields/radio/RadioGroupField.vue +156 -0
  80. package/components/icons/ContainedIcon.vue +130 -0
  81. package/components/images/QRCode.vue +124 -0
  82. package/components/layouts/ContainerWrapper.vue +13 -0
  83. package/components/layouts/ContentBody.vue +30 -0
  84. package/components/layouts/Grid.vue +25 -0
  85. package/components/layouts/Heading.vue +159 -0
  86. package/components/layouts/MainContent.vue +26 -0
  87. package/components/layouts/MaxWidthContainer.vue +15 -0
  88. package/components/layouts/Overtitle.vue +25 -0
  89. package/components/layouts/headers/CompactHeader.vue +181 -0
  90. package/components/layouts/headers/PageHeader.vue +102 -0
  91. package/components/layouts/headers/WebAppHeader.vue +54 -0
  92. package/components/layouts/section/Section.vue +90 -0
  93. package/components/layouts/section/SectionBody.vue +12 -0
  94. package/components/layouts/section/SectionHeader.vue +12 -0
  95. package/components/layouts/section/SectionTitle.vue +13 -0
  96. package/components/lists/List.vue +69 -0
  97. package/components/lists/ListItem.vue +58 -0
  98. package/components/loaders/Loading.vue +83 -0
  99. package/components/loaders/LoadingScreen.vue +285 -0
  100. package/components/modals/DangerModalDialog.vue +149 -0
  101. package/components/modals/InfoModalDialog.vue +143 -0
  102. package/components/modals/ModalActions.vue +22 -0
  103. package/components/modals/ModalContent.vue +5 -0
  104. package/components/modals/ModalDescription.vue +5 -0
  105. package/components/modals/ModalDialog.vue +122 -0
  106. package/components/modals/ModalHeaderGroup.vue +19 -0
  107. package/components/modals/ModalHeadings.vue +5 -0
  108. package/components/modals/ModalSubtitle.vue +14 -0
  109. package/components/modals/ModalTitle.vue +14 -0
  110. package/components/modals/SuccessModalDialog.vue +90 -0
  111. package/components/modules/AppLogo.vue +46 -0
  112. package/components/modules/SVGImage.vue +44 -0
  113. package/components/navigation/links/NavLink.vue +112 -0
  114. package/components/navigation/nav-menu/NavFooterMenu.vue +91 -0
  115. package/components/navigation/nav-menu/NavMenu.vue +36 -0
  116. package/components/navigation/nav-menu/NavMenuItem.vue +44 -0
  117. package/components/navigation/nav-sidebar/BottomUserNavBar.vue +83 -0
  118. package/components/navigation/nav-sidebar/NavSidebar.vue +172 -0
  119. package/components/navigation/nav-sidebar/NavSidebarMenu.vue +14 -0
  120. package/components/navigation/nav-sidebar/NavSidebarMenuItem.vue +76 -0
  121. package/components/navigation/nav-sidebar/NavSidebarMenuSectionTitle.vue +54 -0
  122. package/components/navigation/table-of-contents/TableOfContents.vue +35 -0
  123. package/components/navigation/table-of-contents/TableOfContentsItem.vue +40 -0
  124. package/components/navigation/table-of-contents/TableOfContentsSidebar.vue +29 -0
  125. package/components/pagination/ButtonPagination.vue +274 -0
  126. package/components/pagination/RowsPerPage.vue +60 -0
  127. package/components/pagination/SimplePagination.vue +97 -0
  128. package/components/password/SecurePasswordCondition.vue +41 -0
  129. package/components/password/SecurePasswordConditions.vue +83 -0
  130. package/components/placeholders/ContentPlaceholder.vue +41 -0
  131. package/components/popovers/Popover.vue +128 -0
  132. package/components/rating/InteractiveRating.vue +94 -0
  133. package/components/rating/Rating.vue +60 -0
  134. package/components/rating/RatingItem.vue +54 -0
  135. package/components/skeletons/Skeleton.vue +11 -0
  136. package/components/spinners/Spinner.vue +13 -0
  137. package/components/steppers/CircleStepper.vue +122 -0
  138. package/components/steppers/Step.vue +72 -0
  139. package/components/steppers/StepIndicator.vue +228 -0
  140. package/components/steppers/TabStepper.vue +126 -0
  141. package/components/steppers/vertical-stepper/VerticalStep.vue +223 -0
  142. package/components/steppers/vertical-stepper/VerticalStepper.vue +63 -0
  143. package/components/tables/Table.vue +26 -0
  144. package/components/tables/TableBody.vue +5 -0
  145. package/components/tables/TableCell.vue +34 -0
  146. package/components/tables/TableCellActions.vue +7 -0
  147. package/components/tables/TableHeader.vue +5 -0
  148. package/components/tables/TableHeaderCell.vue +15 -0
  149. package/components/tables/TableRow.vue +14 -0
  150. package/components/tables/TableWrapper.vue +12 -0
  151. package/components/tabs/Tab.vue +145 -0
  152. package/components/tabs/TabBar.vue +64 -0
  153. package/components/tabs/TabContent.vue +5 -0
  154. package/components/tabs/TabsContainer.vue +5 -0
  155. package/components/transitions/HorizontalExpansionTransition.vue +12 -0
  156. package/components/transitions/VerticalExpansionTransition.vue +14 -0
  157. package/components/users/Author.vue +113 -0
  158. package/components/users/User.vue +53 -0
  159. package/composables/useAccordion.ts +12 -0
  160. package/composables/useDarkMode.ts +9 -0
  161. package/composables/useDropdownMenu.ts +25 -0
  162. package/composables/useForm.ts +134 -0
  163. package/composables/useFormValidationMode.ts +11 -0
  164. package/composables/useIsMobile.ts +27 -0
  165. package/composables/useMobileSidebar.ts +32 -0
  166. package/composables/useShiki.ts +12 -0
  167. package/composables/useTableOfContents.ts +50 -0
  168. package/composables/useToastifyConfig.ts +7 -0
  169. package/eslint.config.mjs +14 -0
  170. package/models/constants/app.ts +8 -0
  171. package/models/constants/form.ts +22 -0
  172. package/models/enums/alerts.ts +6 -0
  173. package/models/enums/aspect-ratios.ts +9 -0
  174. package/models/enums/avatars.ts +21 -0
  175. package/models/enums/badges.ts +10 -0
  176. package/models/enums/buttons.ts +38 -0
  177. package/models/enums/colors.ts +9 -0
  178. package/models/enums/content.ts +4 -0
  179. package/models/enums/counters.ts +4 -0
  180. package/models/enums/dividers.ts +9 -0
  181. package/models/enums/dropdowns.ts +18 -0
  182. package/models/enums/effects.ts +6 -0
  183. package/models/enums/emptyPlaceholders.ts +5 -0
  184. package/models/enums/formFields.ts +19 -0
  185. package/models/enums/formValidations.ts +4 -0
  186. package/models/enums/headings.ts +11 -0
  187. package/models/enums/icons.ts +22 -0
  188. package/models/enums/images.ts +16 -0
  189. package/models/enums/lists.ts +10 -0
  190. package/models/enums/loaders.ts +15 -0
  191. package/models/enums/navigation.ts +18 -0
  192. package/models/enums/order.ts +10 -0
  193. package/models/enums/orientations.ts +4 -0
  194. package/models/enums/pages.ts +10 -0
  195. package/models/enums/positions.ts +21 -0
  196. package/models/enums/rating.ts +12 -0
  197. package/models/enums/sections.ts +8 -0
  198. package/models/enums/selects.ts +16 -0
  199. package/models/enums/sliders.ts +4 -0
  200. package/models/enums/steppers.ts +20 -0
  201. package/models/enums/tabs.ts +11 -0
  202. package/models/enums/triggers.ts +4 -0
  203. package/models/types/accordions.ts +6 -0
  204. package/models/types/avatars.ts +4 -0
  205. package/models/types/badges.ts +4 -0
  206. package/models/types/buttons.ts +26 -0
  207. package/models/types/dropdowns.ts +20 -0
  208. package/models/types/forms.ts +14 -0
  209. package/models/types/navigation.ts +11 -0
  210. package/models/types/pagination.ts +4 -0
  211. package/models/types/pdfExportTable.ts +6 -0
  212. package/models/types/radio.ts +9 -0
  213. package/models/types/selects.ts +14 -0
  214. package/models/types/steppers.ts +17 -0
  215. package/models/types/tableOfContent.ts +6 -0
  216. package/models/types/tabs.ts +7 -0
  217. package/nuxt.config.ts +40 -0
  218. package/package.json +57 -0
  219. package/plugins/vue3-toastify.ts +14 -0
  220. package/tsconfig.json +7 -0
@@ -0,0 +1,184 @@
1
+ <template>
2
+ <div
3
+ :class="[
4
+ 'flex flex-col',
5
+ 'w-full',
6
+ 'gap-2'
7
+ ]"
8
+ >
9
+ <!-- Label -->
10
+ <label
11
+ v-if="label"
12
+ :for="id"
13
+ :class="[
14
+ 'text-sm',
15
+ 'font-semibold',
16
+ 'text-left',
17
+ ]"
18
+ >
19
+ {{ label }}
20
+ </label>
21
+
22
+ <!-- Input Container -->
23
+ <div
24
+ :class="[
25
+ 'flex gap-2',
26
+ 'items-center',
27
+ 'border',
28
+ 'rounded-full',
29
+ 'transition-all',
30
+ 'pl-3 pr-4',
31
+ inputSizeClass,
32
+ 'text-text-default',
33
+ 'placeholder-text-neutral-subtler',
34
+ 'border-border-default',
35
+ inputCustomClass,
36
+ isFocused && 'ring-2 ring-border-primary-brand-default',
37
+ disabled ? 'bg-background-neutral-disabled cursor-not-allowed' : 'bg-neutral-white',
38
+ ]"
39
+ >
40
+ <!-- Icon -->
41
+ <span class="text-icon-neutral-subtler">
42
+ <MdiIcon
43
+ icon="mdiMagnify"
44
+ size="20"
45
+ preserveAspectRatio="xMidYMid meet"
46
+ />
47
+ </span>
48
+
49
+ <!-- Input -->
50
+ <input
51
+ :id
52
+ :placeholder
53
+ :value="modelValue"
54
+ :maxlength="maxLength"
55
+ :readonly
56
+ :autocomplete
57
+ :autofocus
58
+ :disabled
59
+ :class="[
60
+ 'w-full',
61
+ 'outline-none',
62
+ 'bg-transparent',
63
+ 'text-sm',
64
+ disabled ? 'cursor-not-allowed' : undefined,
65
+ ]"
66
+ @focus="handleFocus"
67
+ @blur="handleBlur"
68
+ @input="handleInput"
69
+ >
70
+
71
+ <!-- Clear button -->
72
+ <ActionIconButton
73
+ v-if="filled"
74
+ :size="ButtonSize.SM"
75
+ :styleType="ButtonStyleType.NEUTRAL_TRANSPARENT_SUBTLE"
76
+ icon="mdiCloseCircle"
77
+ @click="clearField"
78
+ />
79
+ </div>
80
+
81
+ <!-- Help Text -->
82
+ <p
83
+ v-if="helpText"
84
+ class="text-xs text-left text-text-neutral-subtle"
85
+ >
86
+ {{ helpText }}
87
+ </p>
88
+ </div>
89
+ </template>
90
+
91
+ <script setup lang="ts">
92
+ const props = defineProps({
93
+ id: {
94
+ type: String as PropType<string>,
95
+ required: true,
96
+ },
97
+ label: String as PropType<string>,
98
+ placeholder: {
99
+ type: String as PropType<string>,
100
+ default: 'Search',
101
+ },
102
+ helpText: String as PropType<string>,
103
+ size: {
104
+ type: String as PropType<InputSize>,
105
+ default: InputSize.MD,
106
+ validator: (value: InputSize) => Object.values(InputSize).includes(value),
107
+ },
108
+ modelValue: {
109
+ type: String as PropType<string>,
110
+ default: '',
111
+ },
112
+ maxLength: {
113
+ type: Number as PropType<number>,
114
+ required: true,
115
+ },
116
+ filterAlphabetic: {
117
+ type: Boolean as PropType<boolean>,
118
+ default: false,
119
+ },
120
+ readonly: {
121
+ type: Boolean as PropType<boolean>,
122
+ default: false,
123
+ },
124
+ autocomplete: {
125
+ type: String as PropType<'on' | 'off'>,
126
+ default: 'off',
127
+ },
128
+ autofocus: {
129
+ type: Boolean as PropType<boolean>,
130
+ default: false,
131
+ },
132
+ disabled: {
133
+ type: Boolean as PropType<boolean>,
134
+ default: false,
135
+ },
136
+ inputCustomClass: String as PropType<string>,
137
+ })
138
+
139
+ // States
140
+ const isFocused = ref(false)
141
+
142
+ // Emits
143
+ const emit = defineEmits(['update:modelValue'])
144
+
145
+ // Computed classes
146
+ const inputSizeClass = computed(() => {
147
+ const sizeVariant = {
148
+ [InputSize.MD]: 'h-[36px]',
149
+ [InputSize.LG]: 'h-[44px]',
150
+ }
151
+ return sizeVariant[props.size as InputSize] || 'h-[36px]'
152
+ })
153
+
154
+ // Computed states
155
+ const filled = computed(() => !!props.modelValue.trim())
156
+
157
+ // Methods
158
+ const handleInput = (event: Event) => {
159
+ if (props.disabled) return
160
+
161
+ const target = event.target as HTMLInputElement
162
+ let value = target.value
163
+
164
+ // Apply optional filtering
165
+ if (props.filterAlphabetic) {
166
+ value = filterAlphabetic(value)
167
+ }
168
+
169
+ // Emit the updated value to the parent
170
+ emit('update:modelValue', value)
171
+ }
172
+
173
+ const handleFocus = () => {
174
+ isFocused.value = true
175
+ }
176
+
177
+ const handleBlur = () => {
178
+ isFocused.value = false
179
+ }
180
+
181
+ const clearField = () => {
182
+ emit('update:modelValue', '')
183
+ }
184
+ </script>
@@ -0,0 +1,233 @@
1
+ <template>
2
+ <div
3
+ :class="[
4
+ 'flex flex-col',
5
+ 'w-full',
6
+ 'gap-2'
7
+ ]"
8
+ >
9
+ <!-- Label -->
10
+ <label
11
+ v-if="label"
12
+ :for="id"
13
+ :class="[
14
+ 'text-sm',
15
+ 'font-semibold',
16
+ 'text-left',
17
+ hasError ? 'text-text-error' : undefined
18
+ ]"
19
+ >
20
+ {{ label }}
21
+ </label>
22
+
23
+ <BadgeStack
24
+ v-if="multiple && hasBadgeStack"
25
+ :items="badgesSelectedOptions"
26
+ closeable
27
+ @close="handleBadgeRemove"
28
+ />
29
+
30
+ <DropdownSelect
31
+ :id
32
+ :key="computedPlaceholder"
33
+ :modelValue
34
+ :options
35
+ :type
36
+ :borderClass="hasError ? 'border border-border-error text-text-error' : 'border border-border-default'"
37
+ :placeholder="computedPlaceholder"
38
+ :size
39
+ :activeStyle
40
+ :filterable
41
+ :searchFieldPlaceholder
42
+ :noResultsFoundText
43
+ :disabled
44
+ :dropdownPosition
45
+ :hasSeparator
46
+ :multiple
47
+ :allowDeselect
48
+ :isLoading="isLoadingOptions"
49
+ :loadingText
50
+ @update:modelValue="handleValueUpdate"
51
+ />
52
+
53
+ <!-- Help Text -->
54
+ <p
55
+ v-if="hasError || helpText"
56
+ :class="[
57
+ 'text-xs text-left',
58
+ hasError ? 'text-text-error' : 'text-text-neutral-subtle'
59
+ ]"
60
+ >
61
+ {{ hasError ? error : helpText }}
62
+ </p>
63
+ </div>
64
+ </template>
65
+
66
+ <script setup lang="ts">
67
+ // Props
68
+ const props = defineProps({
69
+ id: {
70
+ type: String as PropType<string>,
71
+ required: true,
72
+ },
73
+ label: String as PropType<string>,
74
+ helpText: String as PropType<string>,
75
+ required: {
76
+ type: Boolean as PropType<boolean>,
77
+ default: false,
78
+ },
79
+ options: {
80
+ type: Array as PropType<SelectOption[]>,
81
+ default: () => [],
82
+ },
83
+ placeholder: {
84
+ type: String as PropType<string>,
85
+ default: 'Select an option',
86
+ },
87
+ modelValue: {
88
+ type: [String, Number, Array] as PropType<string | number | (string | number)[] | null>,
89
+ default: null,
90
+ },
91
+ type: {
92
+ type: String as PropType<SelectType>,
93
+ default: SelectType.TEXT,
94
+ validator: (value: SelectType) => Object.values(SelectType).includes(value),
95
+ },
96
+ size: {
97
+ type: String as PropType<SelectSize>,
98
+ default: SelectSize.MD,
99
+ validator: (value: SelectSize) => Object.values(SelectSize).includes(value),
100
+ },
101
+ activeStyle: {
102
+ type: String as PropType<SelectActiveStyle>,
103
+ default: SelectActiveStyle.CHECK,
104
+ validator: (value: SelectActiveStyle) => Object.values(SelectActiveStyle).includes(value),
105
+ },
106
+ dropdownPosition: {
107
+ type: String as PropType<Position>,
108
+ default: Position.BOTTOM,
109
+ validator: (value: Position) => Object.values(Position).includes(value),
110
+ },
111
+ filterable: {
112
+ type: Boolean as PropType<boolean>,
113
+ default: false,
114
+ },
115
+ searchFieldPlaceholder: {
116
+ type: String as PropType<string>,
117
+ default: 'Search...',
118
+ },
119
+ noResultsFoundText: {
120
+ type: String as PropType<string>,
121
+ default: 'No results found',
122
+ },
123
+ disabled: {
124
+ type: Boolean as PropType<boolean>,
125
+ default: false,
126
+ },
127
+ validator: {
128
+ type: Function as PropType<(value: unknown) => string | null>,
129
+ default: () => null,
130
+ },
131
+ error: {
132
+ type: String as PropType<string>,
133
+ default: '',
134
+ },
135
+ hasSeparator: Boolean as PropType<boolean>,
136
+ multiple: Boolean as PropType<boolean>,
137
+ hasBadgeStack: {
138
+ type: Boolean as PropType<boolean>,
139
+ default: false,
140
+ },
141
+ allowDeselect: {
142
+ type: Boolean as PropType<boolean>,
143
+ default: false,
144
+ },
145
+ isLoading: {
146
+ type: Boolean as PropType<boolean>,
147
+ default: true,
148
+ },
149
+ loadingText: {
150
+ type: String as PropType<string>,
151
+ default: 'Loading options...'
152
+ },
153
+ loadingOptionsPlaceholder: {
154
+ type: String as PropType<string>,
155
+ default: 'Options are being loaded',
156
+ },
157
+ showLoadingState: {
158
+ type: Boolean as PropType<boolean>,
159
+ default: true,
160
+ },
161
+ })
162
+
163
+ // Emits
164
+ const emit = defineEmits(['update:modelValue', 'update:error'])
165
+
166
+ // Composables
167
+ const validationMode = useInjectedValidationMode()
168
+
169
+ // Computed
170
+ const hasError = computed(() => props.error !== '')
171
+ const hasOptions = computed(() => Array.isArray(props.options) && props.options.length > 0)
172
+
173
+ const isLoadingOptions = computed(() => {
174
+ if (!props.showLoadingState) return false
175
+
176
+ return props.isLoading && !hasOptions.value
177
+ })
178
+
179
+ const computedPlaceholder = computed(() => {
180
+ if (props.showLoadingState && props.isLoading && !hasOptions.value) {
181
+ return props.loadingOptionsPlaceholder
182
+ }
183
+
184
+ return props.placeholder
185
+ })
186
+
187
+ // Handlers
188
+ const handleValueUpdate = (newValue: string | number | (string | number)[] | null) => {
189
+ if (props.allowDeselect && !props.multiple) {
190
+ // If it a simple select, deselect on click
191
+ if (newValue === props.modelValue) {
192
+ emit('update:modelValue', null)
193
+ return
194
+ }
195
+ }
196
+
197
+ emit('update:modelValue', newValue)
198
+ }
199
+
200
+ // Methods
201
+ const runValidation = () => {
202
+ if (!props.required || !props.validator) return
203
+
204
+ const result = props.validator(props.modelValue)
205
+
206
+ emit('update:error', result ?? '')
207
+ }
208
+
209
+ const badgesSelectedOptions = computed(() => {
210
+ if (!Array.isArray(props.modelValue)) return []
211
+
212
+ return props.options
213
+ .filter(option => (props.modelValue as (string | number)[]).includes(option.value))
214
+ .map(option => ({
215
+ ...option,
216
+ text: option.text ?? '',
217
+ }))
218
+ })
219
+
220
+ const handleBadgeRemove = (item: SelectOption) => {
221
+ if (!Array.isArray(props.modelValue)) return
222
+
223
+ const updated = props.modelValue.filter(val => val !== item.value)
224
+ emit('update:modelValue', updated)
225
+ }
226
+
227
+ // Watchers
228
+ watch(() => props.modelValue, () => {
229
+ if (validationMode.value === FormValidationMode.BLUR) {
230
+ runValidation()
231
+ }
232
+ })
233
+ </script>