@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,238 @@
1
+ <template>
2
+ <div
3
+ :class="[
4
+ 'flex',
5
+ 'w-full',
6
+ 'gap-4',
7
+ 'justify-between',
8
+ 'items-center',
9
+ 'border border-border-default',
10
+ 'px-3',
11
+ 'py-4',
12
+ 'rounded-lg',
13
+ 'hover:cursor-pointer',
14
+ modelValue === value && selectedBackgroundColorClass,
15
+ disabled && 'opacity-disabled'
16
+ ]"
17
+ @click="selectRadio"
18
+ >
19
+ <!-- Radio Button with Label -->
20
+ <div
21
+ :class="[
22
+ 'flex items-center gap-3',
23
+ 'text-sm',
24
+ 'text-text-default'
25
+ ]"
26
+ >
27
+ <!-- Icon -->
28
+ <div
29
+ :class="[
30
+ 'flex',
31
+ 'justify-center',
32
+ 'items-center',
33
+ 'w-[40px]',
34
+ 'h-[40px]',
35
+ 'rounded-full',
36
+ 'aspect-square',
37
+ modelValue === value ? selectedIconBackgroundColorClass : 'bg-background-neutral-sublter'
38
+ ]"
39
+ >
40
+ <MdiIcon
41
+ :icon="icon"
42
+ size="24"
43
+ preserveAspectRatio="xMidYMid meet"
44
+ :class="modelValue === value ? selectedIconColorClass : 'text-icon-neutral-subtle'"
45
+ />
46
+ </div>
47
+ <!-- Label with help text -->
48
+ <div class="flex flex-col gap-1.5">
49
+ <label
50
+ v-if="label"
51
+ :for="id"
52
+ :class="[
53
+ modelValue === value ? selectedIconColorClass : 'text-text-neutral-default',
54
+ labelSizeClass,
55
+ ]"
56
+ v-html="label"
57
+ />
58
+ <!-- Help Text -->
59
+ <p
60
+ v-if="helpText"
61
+ :class="[
62
+ 'text-xs',
63
+ 'text-left',
64
+ 'text-text-neutral-subtle'
65
+ ]"
66
+ >
67
+ {{ helpText }}
68
+ </p>
69
+ </div>
70
+ </div>
71
+
72
+ <!-- Hidden Native Radio Button -->
73
+ <input
74
+ :id="id"
75
+ type="radio"
76
+ :name="name"
77
+ :value="value"
78
+ :checked="modelValue === value"
79
+ class="hidden"
80
+ :disabled
81
+ @change="selectRadio"
82
+ >
83
+
84
+ <!-- Custom Radio Button -->
85
+ <div
86
+ :class="[
87
+ 'w-[24px] h-[24px]',
88
+ 'aspect-square',
89
+ 'border',
90
+ 'rounded-full',
91
+ 'flex items-center justify-center',
92
+ 'transition-colors',
93
+ modelValue === value ? selectedCheckboxBackgroundColorClass : 'bg-neutral-white border-border-default',
94
+ modelValue === value && disabled && 'border-0',
95
+ disabled ? 'cursor-not-allowed' : 'cursor-pointer'
96
+ ]"
97
+ @click="selectRadio"
98
+ >
99
+ <div
100
+ v-if="modelValue === value"
101
+ :class="[
102
+ 'rounded-full',
103
+ 'w-[50%] h-[50%]',
104
+ 'bg-icon-neutral-on-filled-bg'
105
+ ]"
106
+ />
107
+ </div>
108
+ </div>
109
+ </template>
110
+
111
+ <script setup lang="ts">
112
+ // Props
113
+ const props = defineProps({
114
+ id: {
115
+ type: String as PropType<string>,
116
+ required: true,
117
+ },
118
+ name: { // Group name for radio buttons
119
+ type: String as PropType<string>,
120
+ required: true,
121
+ },
122
+ value: { // Value of the radio button
123
+ type: [String, Number, Boolean] as PropType<string | number | boolean>,
124
+ required: true,
125
+ },
126
+ label: String as PropType<string>,
127
+ helpText: String as PropType<string>,
128
+ icon: {
129
+ type: String as PropType<any>,
130
+ default: 'mdiHelp',
131
+ },
132
+ type: {
133
+ type: String as PropType<
134
+ ColorAccent.INFO | ColorAccent.SUCCESS | ColorAccent.DANGER | ColorAccent.PRIMARY_BRAND | ColorAccent.SECONDARY_BRAND
135
+ >,
136
+ default: ColorAccent.PRIMARY_BRAND,
137
+ validator: (value: ColorAccent) => [
138
+ ColorAccent.INFO,
139
+ ColorAccent.WARNING,
140
+ ColorAccent.DANGER,
141
+ ColorAccent.SUCCESS,
142
+ ColorAccent.PRIMARY_BRAND,
143
+ ColorAccent.SECONDARY_BRAND,
144
+ ].includes(value)
145
+ },
146
+ size: {
147
+ type: String as PropType<ControlFieldSize>,
148
+ default: ControlFieldSize.MD,
149
+ validator: (value: ControlFieldSize) => Object.values(ControlFieldSize).includes(value),
150
+ },
151
+ required: {
152
+ type: Boolean as PropType<boolean>,
153
+ default: false,
154
+ },
155
+ modelValue: {
156
+ type: [String, Number, Boolean, null] as PropType<string | number | boolean | null>,
157
+ default: null,
158
+ },
159
+ validator: {
160
+ type: Function as PropType<(value: any) => boolean>,
161
+ default: () => null,
162
+ },
163
+ disabled: {
164
+ type: Boolean as PropType<boolean>,
165
+ default: false,
166
+ },
167
+ })
168
+
169
+ // Emits
170
+ const emit = defineEmits(['update:modelValue', 'validate'])
171
+
172
+ // Computed classes
173
+ const labelSizeClass = computed(() => {
174
+ const sizeVariant = {
175
+ [ControlFieldSize.MD]: 'text-sm',
176
+ [ControlFieldSize.LG]: 'text-base',
177
+ }
178
+ return sizeVariant[props.size as ControlFieldSize] || 'text-sm'
179
+ })
180
+
181
+ const selectedBackgroundColorClass = computed(() => {
182
+ const colorVariant = {
183
+ [ColorAccent.INFO]: 'bg-background-info-subtlest',
184
+ [ColorAccent.WARNING]: 'bg-background-warning-subtlest',
185
+ [ColorAccent.DANGER]: 'bg-background-danger-subtlest',
186
+ [ColorAccent.SUCCESS]: 'bg-background-success-subtlest',
187
+ [ColorAccent.NEUTRAL]: 'bg-background-primary-brand-checked-subtlest',
188
+ [ColorAccent.PRIMARY_BRAND]: 'bg-background-primary-brand-checked-subtlest',
189
+ [ColorAccent.SECONDARY_BRAND]: 'bg-background-secondary-brand-checked-subtlest',
190
+ }
191
+ return colorVariant[props.type as ColorAccent] || 'bg-background-primary-brand-checked-subtlest'
192
+ })
193
+
194
+ const selectedIconBackgroundColorClass = computed(() => {
195
+ const colorVariant = {
196
+ [ColorAccent.INFO]: 'bg-background-info-subtlest',
197
+ [ColorAccent.WARNING]: 'bg-background-warning-subtle-plus',
198
+ [ColorAccent.DANGER]: 'bg-background-danger-subtle-plus',
199
+ [ColorAccent.SUCCESS]: 'bg-background-success-subtle-plus',
200
+ [ColorAccent.PRIMARY_BRAND]: 'bg-background-primary-brand-on-checked-subtle-bg',
201
+ [ColorAccent.SECONDARY_BRAND]: 'bg-background-secondary-brand-on-checked-subtle-bg',
202
+ [ColorAccent.NEUTRAL]: undefined,
203
+ }
204
+ return colorVariant[props.type as ColorAccent] || 'bg-background-primary-brand-on-checked-subtle-bg'
205
+ })
206
+
207
+ const selectedIconColorClass = computed(() => {
208
+ const colorVariant = {
209
+ [ColorAccent.INFO]: 'text-icon-info',
210
+ [ColorAccent.WARNING]: 'text-icon-warning',
211
+ [ColorAccent.DANGER]: 'text-icon-danger',
212
+ [ColorAccent.SUCCESS]: 'text-icon-success',
213
+ [ColorAccent.PRIMARY_BRAND]: 'text-icon-primary-brand-active',
214
+ [ColorAccent.SECONDARY_BRAND]: 'text-icon-secondary-brand-active',
215
+ [ColorAccent.NEUTRAL]: undefined,
216
+ }
217
+ return colorVariant[props.type as ColorAccent] || 'text-icon-primary-brand-active'
218
+ })
219
+
220
+ const selectedCheckboxBackgroundColorClass = computed(() => {
221
+ const colorVariant = {
222
+ [ColorAccent.INFO]: 'bg-background-info-bold',
223
+ [ColorAccent.WARNING]: 'bg-background-warning-bold',
224
+ [ColorAccent.DANGER]: 'bg-background-danger-bold',
225
+ [ColorAccent.SUCCESS]: 'bg-background-success-bold',
226
+ [ColorAccent.PRIMARY_BRAND]: 'bg-background-primary-brand-checked',
227
+ [ColorAccent.SECONDARY_BRAND]: 'bg-background-secondary-brand-checked',
228
+ [ColorAccent.NEUTRAL]: undefined,
229
+ }
230
+ return colorVariant[props.type as ColorAccent] || 'bg-background-primary-brand-checked'
231
+ })
232
+
233
+ // Handlers
234
+ const selectRadio = () => {
235
+ if (props.disabled) return
236
+ emit('update:modelValue', props.value)
237
+ }
238
+ </script>
@@ -0,0 +1,157 @@
1
+ <template>
2
+ <div :class="[ 'flex flex-col', 'w-full', 'gap-2' ]">
3
+ <!-- Radio Button with Label -->
4
+ <div
5
+ :class="[
6
+ 'flex items-center gap-3',
7
+ 'text-sm',
8
+ 'text-text-default'
9
+ ]"
10
+ >
11
+ <!-- Label (inverted position) -->
12
+ <label
13
+ v-if="inverse"
14
+ :for="id"
15
+ :class="[
16
+ disabled && 'text-text-neutral-disabled',
17
+ labelSizeClass
18
+ ]"
19
+ v-html="label"
20
+ />
21
+
22
+ <!-- Hidden Native Radio Button -->
23
+ <input
24
+ :id="id"
25
+ type="radio"
26
+ :name="name"
27
+ :value="value"
28
+ :checked="modelValue === value"
29
+ class="hidden"
30
+ :disabled="disabled"
31
+ @change="selectRadio"
32
+ >
33
+
34
+ <!-- Custom Radio Button -->
35
+ <div
36
+
37
+ :class="[
38
+ radioSizeClass,
39
+ 'aspect-square',
40
+ 'border',
41
+ 'rounded-full',
42
+ 'flex items-center justify-center',
43
+ 'transition-colors',
44
+ modelValue === value ? 'bg-background-primary-brand-checked' : 'bg-neutral-white border-border-default',
45
+ disabled ? 'bg-background-neutral-disabled cursor-not-allowed' : 'cursor-pointer'
46
+ ]"
47
+ @click="selectRadio"
48
+ >
49
+ <div
50
+ v-if="modelValue === value"
51
+ :class="[
52
+ 'rounded-full',
53
+ 'w-[50%] h-[50%]',
54
+ disabled ? 'bg-icon-neutral-disabled' : 'bg-icon-neutral-on-filled-bg'
55
+ ]"
56
+ />
57
+ </div>
58
+
59
+ <!-- Label (natural position) -->
60
+ <label
61
+ v-if="!inverse"
62
+ :for="id"
63
+ :class="[
64
+ disabled ? 'text-text-neutral-disabled' : undefined,
65
+ labelSizeClass
66
+ ]"
67
+ v-html="label"
68
+ />
69
+ </div>
70
+
71
+ <!-- Help Text -->
72
+ <p
73
+ v-if="helpText"
74
+ :class="[
75
+ 'text-xs',
76
+ 'text-left',
77
+ 'text-text-neutral-subtle'
78
+ ]"
79
+ >
80
+ {{ helpText }}
81
+ </p>
82
+ </div>
83
+ </template>
84
+
85
+ <script setup lang="ts">
86
+ // Props
87
+ const props = defineProps({
88
+ id: {
89
+ type: String as PropType<string>,
90
+ required: true,
91
+ },
92
+ name: { // Group name for radio buttons
93
+ type: String as PropType<string>,
94
+ required: true,
95
+ },
96
+ value: { // Value of the radio button
97
+ type: [String, Number, Boolean] as PropType<string | number | boolean>,
98
+ required: true,
99
+ },
100
+ label: {
101
+ type: String as PropType<string>,
102
+ default: 'Text',
103
+ },
104
+ helpText: String as PropType<string>,
105
+ required: {
106
+ type: Boolean as PropType<boolean>,
107
+ default: false,
108
+ },
109
+ modelValue: {
110
+ type: [String, Number, Boolean, null] as PropType<string | number | boolean | null>,
111
+ default: null,
112
+ },
113
+ validator: {
114
+ type: Function as PropType<(value: any) => boolean>,
115
+ default: () => null,
116
+ },
117
+ disabled: {
118
+ type: Boolean as PropType<boolean>,
119
+ default: false,
120
+ },
121
+ size: {
122
+ type: String as PropType<ControlFieldSize>,
123
+ default: ControlFieldSize.MD,
124
+ validator: (value: ControlFieldSize) => Object.values(ControlFieldSize).includes(value),
125
+ },
126
+ inverse: { // Sets the radio button on the right side of the text
127
+ type: Boolean as PropType<boolean>,
128
+ default: false,
129
+ },
130
+ })
131
+
132
+ // Emits
133
+ const emit = defineEmits(['update:modelValue', 'validate'])
134
+
135
+ // Computed classes
136
+ const radioSizeClass = computed(() => {
137
+ const sizeVariant = {
138
+ [ControlFieldSize.MD]: 'w-[24px] h-[24px]',
139
+ [ControlFieldSize.LG]: 'w-[32px] h-[32px]',
140
+ }
141
+ return sizeVariant[props.size as ControlFieldSize] || 'w-[24px] h-[24px]'
142
+ })
143
+
144
+ const labelSizeClass = computed(() => {
145
+ const sizeVariant = {
146
+ [ControlFieldSize.MD]: 'text-sm',
147
+ [ControlFieldSize.LG]: 'text-base',
148
+ }
149
+ return sizeVariant[props.size as ControlFieldSize] || 'text-sm'
150
+ })
151
+
152
+ // Handlers
153
+ const selectRadio = () => {
154
+ if (props.disabled) return
155
+ emit('update:modelValue', props.value)
156
+ }
157
+ </script>
@@ -0,0 +1,156 @@
1
+ <template>
2
+ <div class="w-full flex flex-col gap-2">
3
+ <!-- Label -->
4
+ <div
5
+ v-if="label"
6
+ :class="[
7
+ 'text-sm',
8
+ 'font-semibold',
9
+ 'text-left',
10
+ hasError ? 'text-text-error' : undefined
11
+ ]"
12
+ >
13
+ {{ label }}
14
+ </div>
15
+ <div
16
+ :class="[
17
+ orientation === Orientation.VERTICAL
18
+ ? 'flex flex-col gap-4'
19
+ : [
20
+ 'w-full',
21
+ 'grid gap-4',
22
+ 'grid-cols-1',
23
+ 'sm:grid-cols-2',
24
+ ]
25
+ ]"
26
+ >
27
+ <template v-if="type === RadioType.DEFAULT">
28
+ <RadioField
29
+ v-for="option in options"
30
+ :id="option.id.toString()"
31
+ :key="option.id"
32
+ v-model="selectedOption"
33
+ :name="name"
34
+ :value="option.value"
35
+ :label="option.label"
36
+ :required="required"
37
+ :helpText="option.helpText"
38
+ :disabled="option.disabled"
39
+ :inverse="inverse"
40
+ />
41
+ </template>
42
+
43
+ <template v-if="type === RadioType.BUTTON">
44
+ <RadioButtonField
45
+ v-for="option in options"
46
+ :id="option.id.toString()"
47
+ :key="option.id"
48
+ v-model="selectedOption"
49
+ :name="name"
50
+ :value="option.value"
51
+ :label="option.label"
52
+ :required="required"
53
+ :helpText="option.helpText"
54
+ :disabled="option.disabled"
55
+ :type="option.type"
56
+ :icon="option.icon"
57
+ />
58
+ </template>
59
+ </div>
60
+
61
+ <!-- Help Text or Error Message -->
62
+ <p
63
+ v-if="hasError || helpText"
64
+ :class="[
65
+ 'text-xs text-left',
66
+ hasError ? 'text-text-error' : 'text-text-neutral-subtle'
67
+ ]"
68
+ >
69
+ {{ hasError ? error : helpText }}
70
+ </p>
71
+ </div>
72
+ </template>
73
+
74
+ <script setup lang="ts">
75
+
76
+ // Props
77
+ const props = defineProps({
78
+ label: String,
79
+ name: {
80
+ type: String as PropType<string>,
81
+ required: true,
82
+ },
83
+ options: {
84
+ type: Array as PropType<RadioOption[]>,
85
+ required: true,
86
+ validator: (value: RadioOption[]) =>
87
+ value.every((option) => option.id !== undefined && option.value !== undefined && option.label !== undefined),
88
+ },
89
+ modelValue: {
90
+ type: [String, Number, Boolean, null] as PropType<string | number | boolean | null>,
91
+ required: true,
92
+ },
93
+ validator: {
94
+ type: Function as PropType<(value: unknown) => string | null>,
95
+ default: () => null,
96
+ },
97
+ error: {
98
+ type: String as PropType<string>,
99
+ default: '',
100
+ },
101
+ type: {
102
+ type: String as PropType<RadioType>,
103
+ default: RadioType.DEFAULT,
104
+ validator: (value: RadioType) => Object.values(RadioType).includes(value),
105
+ },
106
+ required: {
107
+ type: Boolean as PropType<boolean>,
108
+ default: false,
109
+ },
110
+ helpText: String as PropType<string>,
111
+ inverse: { // Sets the radio button on the right side of the text
112
+ type: Boolean as PropType<boolean>,
113
+ default: false,
114
+ },
115
+ orientation: {
116
+ type: String as PropType<Orientation>,
117
+ default: Orientation.VERTICAL,
118
+ validator: (value: Orientation) => Object.values(Orientation).includes(value),
119
+ },
120
+ })
121
+
122
+ // States
123
+ const hasError = computed(() => props.error !== '')
124
+ const selectedOption = computed({
125
+ get: () => props.modelValue,
126
+ set: (value) => {
127
+ emit('update:modelValue', value)
128
+ runValidation()
129
+ },
130
+ })
131
+
132
+ // Composables
133
+ const validationMode = useInjectedValidationMode()
134
+
135
+ // Emits
136
+ const emit = defineEmits(['update:modelValue', 'update:error'])
137
+
138
+ // Methods
139
+ const runValidation = () => {
140
+ if (!props.required || !props.validator || validationMode.value !== FormValidationMode.BLUR) return
141
+
142
+ const result = props.validator(props.modelValue)
143
+
144
+ emit('update:error', result ?? '')
145
+ }
146
+
147
+
148
+ // Watchers
149
+ // Watch for changes in local selectedOption and emit upward
150
+ watch(
151
+ selectedOption,
152
+ () => {
153
+ runValidation()
154
+ },
155
+ )
156
+ </script>
@@ -0,0 +1,130 @@
1
+ <template>
2
+ <div
3
+ :class="[
4
+ 'flex',
5
+ 'items-center',
6
+ 'justify-center',
7
+ 'rounded-full',
8
+ 'aspect-square',
9
+ iconContainerClass,
10
+ iconContainerShapeClass,
11
+ iconContainerSizeClass,
12
+ ]"
13
+ >
14
+ <MdiIcon
15
+ :icon="icon"
16
+ :size="iconSizeClass"
17
+ preserveAspectRatio="xMidYMid meet"
18
+ :class="iconColorClass"
19
+ />
20
+ </div>
21
+ </template>
22
+ <script setup lang="ts">
23
+ // Props
24
+ const props = defineProps({
25
+ styleType: {
26
+ type: String as PropType<IconContainerStyleType>,
27
+ default: IconContainerStyleType.FLAT,
28
+ validator: (value: IconContainerStyleType) => Object.values(IconContainerStyleType).includes(value),
29
+ },
30
+ icon: {
31
+ type: String as PropType<any>,
32
+ default: 'mdiHelp',
33
+ },
34
+ shape: {
35
+ type: String as PropType<IconContainerShape>,
36
+ default: IconContainerShape.CIRCLE,
37
+ validator: (value: IconContainerShape) => Object.values(IconContainerShape).includes(value),
38
+ },
39
+ color: {
40
+ type: String as PropType<ColorAccent>,
41
+ default: ColorAccent.NEUTRAL,
42
+ validator: (value: ColorAccent) => Object.values(ColorAccent).includes(value),
43
+ },
44
+ size: {
45
+ type: String as PropType<IconContainerSize>,
46
+ default: IconContainerSize.XL,
47
+ validator: (value: IconContainerSize) => Object.values(IconContainerSize).includes(value),
48
+ },
49
+ })
50
+
51
+ // Computed classes
52
+ const iconContainerShapeClass = computed(() => {
53
+ const shapeVariants = {
54
+ [IconContainerShape.CIRCLE]: 'rounded-full',
55
+ [IconContainerShape.SQUARE]: 'rounded-lg',
56
+ }
57
+ return shapeVariants[props.shape as IconContainerShape] || 'rounded-full'
58
+ })
59
+
60
+ const iconContainerClass = computed(() => {
61
+ const filled = props.styleType === IconContainerStyleType.FILLED
62
+
63
+ const colorVariants: Record<ColorAccent, string> = {
64
+ [ColorAccent.NEUTRAL]: filled
65
+ ? 'bg-background-neutral-bold'
66
+ : 'bg-background-neutral-sublter',
67
+ [ColorAccent.SUCCESS]: filled
68
+ ? 'bg-background-success-bold'
69
+ : 'bg-background-success-subtler',
70
+ [ColorAccent.WARNING]: filled
71
+ ? 'bg-background-warning-bold'
72
+ : 'bg-background-warning-subtler',
73
+ [ColorAccent.DANGER]: filled
74
+ ? 'bg-background-danger-bold'
75
+ : 'bg-background-danger-subtler',
76
+ [ColorAccent.INFO]: filled
77
+ ? 'bg-background-info-bold'
78
+ : 'bg-background-info-subtler',
79
+ [ColorAccent.PRIMARY_BRAND]: filled
80
+ ? 'bg-background-primary-brand-default'
81
+ : 'bg-background-primary-brand-soft',
82
+ [ColorAccent.SECONDARY_BRAND]: filled
83
+ ? 'bg-background-secondary-brand-default'
84
+ : 'bg-background-secondary-brand-soft',
85
+ }
86
+
87
+ return colorVariants[props.color as ColorAccent] || (filled
88
+ ? 'bg-background-neutral-bold'
89
+ : 'bg-background-neutral-sublter')
90
+ })
91
+
92
+ const iconColorClass = computed(() => {
93
+ if (props.styleType === IconContainerStyleType.FILLED) return 'text-icon-neutral-on-filled-bg'
94
+
95
+ const iconVariants = {
96
+ [ColorAccent.NEUTRAL]: 'text-icon-neutral-subtle-on-subtler-bg',
97
+ [ColorAccent.SUCCESS]: 'text-icon-success',
98
+ [ColorAccent.WARNING]: 'text-icon-warning-on-bg',
99
+ [ColorAccent.DANGER]: 'text-icon-danger',
100
+ [ColorAccent.INFO]: 'text-icon-info',
101
+ [ColorAccent.PRIMARY_BRAND]: 'text-icon-primary-brand-default',
102
+ [ColorAccent.SECONDARY_BRAND]: 'text-icon-secondary-brand-default',
103
+ }
104
+ return iconVariants[props.color as ColorAccent] || 'text-icon-neutral-subtle-on-subtler-bg'
105
+
106
+ })
107
+
108
+ const iconContainerSizeClass = computed(() => {
109
+ const sizeVariants = {
110
+ [IconContainerSize.LG]: 'w-[40px] h-[40px]',
111
+ [IconContainerSize.XL]: 'w-[48px] h-[48px]',
112
+ [IconContainerSize.XXL]: 'w-[56px] h-[56px]',
113
+ [IconContainerSize.XXXL]: 'w-[80px] h-[80px]',
114
+ }
115
+
116
+ return sizeVariants[props.size as IconContainerSize] || 'w-[48px] h-[48px]'
117
+ })
118
+
119
+ const iconSizeClass = computed(() => {
120
+ const sizeVariants = {
121
+ [IconContainerSize.LG]: '24',
122
+ [IconContainerSize.XL]: '24',
123
+ [IconContainerSize.XXL]: '40',
124
+ [IconContainerSize.XXXL]: '48',
125
+ }
126
+
127
+ return sizeVariants[props.size as IconContainerSize] || '24'
128
+ })
129
+
130
+ </script>