@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,159 @@
1
+ <template>
2
+ <div
3
+ :class="[
4
+ 'w-full',
5
+ 'flex gap-3',
6
+ description === undefined || description === '' ? 'px-4 py-2' : 'p-4',
7
+ 'rounded-lg',
8
+ 'border',
9
+ containerClass,
10
+ ]"
11
+ >
12
+ <template v-if="description">
13
+ <MdiIcon
14
+ :icon="iconType"
15
+ size="20"
16
+ preserveAspectRatio="xMidYMid meet"
17
+ :class="['min-w-[20px]', iconColorClass]"
18
+ />
19
+ <div
20
+ :class="[
21
+ 'w-full flex flex-col gap-2',
22
+ textColorClass,
23
+ ]"
24
+ >
25
+ <span class="text-sm font-semibold"
26
+ >
27
+ {{ title }}
28
+ </span>
29
+ <p v-html="description" class="text-sm" />
30
+
31
+ <div
32
+ v-if="buttons?.length"
33
+ class="flex gap-6"
34
+ >
35
+ <AlertButton
36
+ v-for="(button, index) in buttons" :key="`text-${index}`"
37
+ :type
38
+ :actionType="button.actionType"
39
+ :text="button.text"
40
+ :icon="button.icon"
41
+ :iconPosition="button.iconPosition"
42
+ :disabled="button.disabled"
43
+ :to="button.to"
44
+ :isExternal="button.isExternal"
45
+ />
46
+ </div>
47
+ </div>
48
+ </template>
49
+
50
+ <template v-else>
51
+ <div class="w-full flex gap-3">
52
+ <div class="w-full flex gap-3 pt-1.5">
53
+ <MdiIcon
54
+ :icon="iconType"
55
+ size="20"
56
+ preserveAspectRatio="xMidYMid meet"
57
+ :class="['min-w-[20px]', iconColorClass]"
58
+ />
59
+ <span :class="['text-sm font-semibold', textColorClass]">
60
+ {{ title }}
61
+ </span>
62
+ </div>
63
+
64
+ <div class="flex gap-3">
65
+ <div
66
+ v-if="buttons?.length"
67
+ class="flex gap-4"
68
+ >
69
+ <AlertButton
70
+ v-for="(button, index) in buttons" :key="`text-${index}`"
71
+ :type
72
+ :actionType="button.actionType"
73
+ :text="button.text"
74
+ :icon="button.icon"
75
+ :iconPosition="button.iconPosition"
76
+ :disabled="button.disabled"
77
+ :to="button.to"
78
+ :isExternal="button.isExternal"
79
+ :callback="button.callback"
80
+ />
81
+ </div>
82
+
83
+ <AlertIconButton
84
+ v-if="hasCloseButton"
85
+ :type
86
+ icon="mdiClose"
87
+ @click="$emit('close')"
88
+ />
89
+ </div>
90
+ </div>
91
+ </template>
92
+ </div>
93
+ </template>
94
+ <script setup lang="ts">
95
+ // Props
96
+ const props = defineProps({
97
+ type: {
98
+ type: String as PropType<AlertType>,
99
+ default: AlertType.WARNING,
100
+ validator: (value: AlertType) => Object.values(AlertType).includes(value),
101
+ },
102
+ icon: String as PropType<any>,
103
+ title: {
104
+ type: String as PropType<string>,
105
+ default: 'Title',
106
+ },
107
+ description: String as PropType<string>,
108
+ buttons: Array as PropType<AlertButton[]>,
109
+ hasCloseButton: {
110
+ type: Boolean as PropType<boolean>,
111
+ default: true,
112
+ },
113
+ })
114
+
115
+ // Emits
116
+ defineEmits(['close'])
117
+
118
+ // Computed classes
119
+ const containerClass = computed(() => {
120
+ const variant = {
121
+ [AlertType.WARNING]: 'bg-background-warning-subtler border-border-warning',
122
+ [AlertType.DANGER]: 'bg-background-danger-subtler border-border-danger',
123
+ [AlertType.SUCCESS]: 'bg-background-success-subtler border-border-success',
124
+ [AlertType.INFO]: 'bg-background-info-subtler border-border-info',
125
+ }
126
+ return variant[props.type as AlertType] || 'bg-background-warning-subtler border-border-warning'
127
+ })
128
+
129
+ const textColorClass = computed(() => {
130
+ const variant = {
131
+ [AlertType.WARNING]: 'text-text-warning-on-bg',
132
+ [AlertType.DANGER]: 'text-text-danger',
133
+ [AlertType.SUCCESS]: 'text-text-success',
134
+ [AlertType.INFO]: 'text-text-info',
135
+ }
136
+ return variant[props.type as AlertType] || 'text-text-warning-on-bg'
137
+ })
138
+
139
+ const iconColorClass = computed(() => {
140
+ const variant = {
141
+ [AlertType.WARNING]: 'text-icon-warning-on-bg',
142
+ [AlertType.DANGER]: 'text-icon-danger',
143
+ [AlertType.SUCCESS]: 'text-icon-success',
144
+ [AlertType.INFO]: 'text-icon-info',
145
+ }
146
+ return variant[props.type as AlertType] || 'text-icon-warning-on-bg'
147
+ })
148
+
149
+ const iconType = computed(() => {
150
+ const variant = {
151
+ [AlertType.WARNING]: 'mdiAlertOutline',
152
+ [AlertType.DANGER]: 'mdiCloseOctagonOutline',
153
+ [AlertType.SUCCESS]: 'mdiCheckCircleOutline',
154
+ [AlertType.INFO]: 'mdiInformationOutline',
155
+ }
156
+ return variant[props.type as AlertType] as any || 'mdiAlertOutline'
157
+ })
158
+
159
+ </script>
@@ -0,0 +1,152 @@
1
+ <template>
2
+ <div
3
+ :class="[
4
+ 'flex justify-center items-center',
5
+ 'bg-background-primary-brand-soft',
6
+ 'aspect-square',
7
+ 'overflow-hidden',
8
+ shapeClass,
9
+ sizeClass,
10
+ hasBorder && [
11
+ 'border border-border-primary-brand-hover',
12
+ borderSizeClass,
13
+ ],
14
+ isInteractive && [
15
+ hoverBorderSizeClass,
16
+ 'border-border-primary-brand-hover cursor-pointer'
17
+ ],
18
+ ]"
19
+ >
20
+ <!-- Show initials if imgUrl is not provided or image fails to load -->
21
+ <span
22
+ v-if="!props.imgUrl || !isImageLoaded"
23
+ :class="[
24
+ 'flex items-center justify-center text-text-primary-brand-on-soft-bg font-bold select-none',
25
+ textSizeClass,
26
+ ]"
27
+ >
28
+ {{ initials }}
29
+ </span>
30
+
31
+ <!-- Show the image if it loads successfully -->
32
+ <img
33
+ v-else
34
+ :src="props.imgUrl"
35
+ :alt="`${props.displayName} avatar`"
36
+ class="object-cover w-full h-full aspect-square"
37
+ @load="handleImageLoad"
38
+ @error="handleImageError"
39
+ >
40
+ </div>
41
+ </template>
42
+
43
+ <script setup lang="ts">
44
+ // Props
45
+ const props = defineProps({
46
+ shape: {
47
+ type: String as PropType<AvatarShape>,
48
+ default: AvatarShape.CIRCLE,
49
+ validator: (value: AvatarShape) => Object.values(AvatarShape).includes(value),
50
+ },
51
+ size: {
52
+ type: String as PropType<AvatarSize | AvatarStackSize>,
53
+ default: AvatarSize.SM,
54
+ validator: (value: AvatarSize) => Object.values(AvatarSize).includes(value),
55
+ },
56
+ isInteractive: {
57
+ type: Boolean as PropType<boolean>,
58
+ default: false,
59
+ },
60
+ displayName: {
61
+ type: String as PropType<string>,
62
+ default: 'Test user',
63
+ },
64
+ imgUrl: String as PropType<string>,
65
+ hasBorder: {
66
+ type: Boolean as PropType<boolean>,
67
+ default: false,
68
+ },
69
+ })
70
+
71
+ // States
72
+ const isImageLoaded = ref(true)
73
+
74
+ // Handlers for image load and error
75
+ const handleImageLoad = () => {
76
+ isImageLoaded.value = true
77
+ }
78
+
79
+ const handleImageError = () => {
80
+ isImageLoaded.value = false
81
+ }
82
+
83
+ // Computed classes
84
+ const shapeClass = computed(() => {
85
+ const shapeVariant = {
86
+ [AvatarShape.CIRCLE]: 'rounded-full',
87
+ [AvatarShape.SQUARE]: 'rounded-md',
88
+ }
89
+ return shapeVariant[props.shape as AvatarShape] || 'rounded-full'
90
+ })
91
+
92
+ const sizeClass = computed(() => {
93
+ const sizeVariant = {
94
+ [AvatarSize.XS]: 'w-[24px] h-[24px]',
95
+ [AvatarSize.SM]: 'w-[32px] h-[32px]',
96
+ [AvatarSize.MD]: 'w-[36px] h-[36px]',
97
+ [AvatarSize.LG]: 'w-[40px] h-[40px]',
98
+ [AvatarSize.XL]: 'w-[48px] h-[48px]',
99
+ [AvatarSize.XXL]: 'w-[56px] h-[56px]',
100
+ [AvatarSize.XXXL]: 'w-[96px] h-[96px]',
101
+ [AvatarSize.XXXXL]: 'w-[224px] h-[224px]',
102
+ }
103
+ return sizeVariant[props.size as AvatarSize] || 'w-[32px] h-[32px]'
104
+ })
105
+
106
+ const textSizeClass = computed(() => {
107
+ const textSizeVariant = {
108
+ [AvatarSize.XS]: 'text-xs',
109
+ [AvatarSize.SM]: 'text-sm',
110
+ [AvatarSize.MD]: 'text-md',
111
+ [AvatarSize.LG]: 'text-md',
112
+ [AvatarSize.XL]: 'text-lg',
113
+ [AvatarSize.XXL]: 'text-xl',
114
+ [AvatarSize.XXXL]: 'text-3xl',
115
+ [AvatarSize.XXXXL]: 'text-4xl',
116
+ }
117
+ return textSizeVariant[props.size as AvatarSize] || 'text-sm'
118
+ })
119
+
120
+ const borderSizeClass = computed(() => {
121
+ const sizeVariant = {
122
+ [AvatarSize.XS]: 'border-1',
123
+ [AvatarSize.SM]: 'border-2',
124
+ [AvatarSize.MD]: 'border-2',
125
+ [AvatarSize.LG]: 'border-2',
126
+ [AvatarSize.XL]: 'border-2',
127
+ [AvatarSize.XXL]: 'border-2',
128
+ [AvatarSize.XXXL]: 'border-4',
129
+ [AvatarSize.XXXXL]: 'border-4',
130
+ }
131
+
132
+ return sizeVariant[props.size as AvatarSize] || 'border-2'
133
+ })
134
+
135
+ const hoverBorderSizeClass = computed(() => {
136
+ const sizeVariant = {
137
+ [AvatarSize.XS]: 'hover:border-1',
138
+ [AvatarSize.SM]: 'hover:border-2',
139
+ [AvatarSize.MD]: 'hover:border-2',
140
+ [AvatarSize.LG]: 'hover:border-2',
141
+ [AvatarSize.XL]: 'hover:border-2',
142
+ [AvatarSize.XXL]: 'hover:border-2',
143
+ [AvatarSize.XXXL]: 'hover:border-4',
144
+ [AvatarSize.XXXXL]: 'hover:border-4',
145
+ }
146
+
147
+ return sizeVariant[props.size as AvatarSize] || 'hover:border-2'
148
+ })
149
+
150
+ // Computed initials
151
+ const initials = computed(() => getInitials(props.displayName))
152
+ </script>
@@ -0,0 +1,97 @@
1
+ <template>
2
+ <div class="flex gap-2">
3
+ <Avatar
4
+ v-for="(avatar, index) in filteredItems"
5
+ :key="avatar.displayName"
6
+ :displayName="avatar.displayName"
7
+ :imgUrl="avatar.imgUrl"
8
+ hasBorder
9
+ :isInteractive
10
+ :shape
11
+ :size
12
+ :class="[
13
+ index !== 0 && '-ml-4',
14
+ 'border-2',
15
+ isInteractive && hoveredIndex === index
16
+ ? 'border-border-primary-brand-hover'
17
+ : '!border-border-neutral-stacked',
18
+ ]"
19
+ @mouseenter="onMouseEnter(index)"
20
+ @mouseleave="onMouseLeave"
21
+ />
22
+ <AvatarStackCounter
23
+ v-if="Number.isFinite(itemsLimit) && itemsLimit && items.length > itemsLimit"
24
+ :text="counterContent"
25
+ :shape
26
+ :size
27
+ class="-ml-4"
28
+ />
29
+ </div>
30
+
31
+ </template>
32
+ <script setup lang="ts">
33
+ // Props
34
+ const props = defineProps({
35
+ items: {
36
+ type: Array as PropType<Avatar[]>,
37
+ default: () => [
38
+ {
39
+ displayName: 'John Doe',
40
+ },
41
+ {
42
+ displayName: 'Sarah Penny',
43
+ },
44
+ {
45
+ displayName: 'Rachel Kross'
46
+ },
47
+ {
48
+ displayName: 'Jadie Miller'
49
+ },
50
+ {
51
+ displayName: 'Alicia Mels',
52
+ },
53
+ ]
54
+ },
55
+ shape: {
56
+ type: String as PropType<AvatarShape>,
57
+ default: AvatarShape.CIRCLE,
58
+ validator: (value: AvatarShape) => Object.values(AvatarShape).includes(value),
59
+ },
60
+ size: {
61
+ type: String as PropType<AvatarStackSize>,
62
+ default: AvatarStackSize.SM,
63
+ validator: (value: AvatarStackSize) => Object.values(AvatarStackSize).includes(value),
64
+ },
65
+ isInteractive: Boolean as PropType<boolean>,
66
+ itemsLimit: {
67
+ type: Number as PropType<number | null>,
68
+ default: null
69
+ },
70
+ counterType: {
71
+ type: String as PropType<StackCounterType>,
72
+ default: StackCounterType.ELLIPSIS,
73
+ validator: (value: StackCounterType) => Object.values(StackCounterType).includes(value),
74
+ }
75
+ })
76
+
77
+ // Computed
78
+ const filteredItems = computed(() => {
79
+ return props.itemsLimit !== null && props.itemsLimit !== undefined
80
+ ? props.items.filter((_, index) => index < props.itemsLimit!)
81
+ : props.items
82
+ })
83
+
84
+ const counterContent = computed(() => getStackCounterContent(props.items, props.counterType, props.itemsLimit ? props.itemsLimit : null))
85
+
86
+ // States
87
+ const hoveredIndex = ref<number | null>(null)
88
+
89
+ // Handlers
90
+ const onMouseEnter = (index: number) => {
91
+ hoveredIndex.value = index
92
+ }
93
+
94
+ const onMouseLeave = () => {
95
+ hoveredIndex.value = null
96
+ }
97
+ </script>
@@ -0,0 +1,74 @@
1
+ <template>
2
+ <div
3
+ :class="[
4
+ 'flex justify-center items-center',
5
+ 'bg-background-primary-brand-soft',
6
+ 'aspect-square',
7
+ 'overflow-hidden',
8
+ shapeClass,
9
+ sizeClass,
10
+ 'border-2 border-border-neutral-stacked',
11
+ ]"
12
+ >
13
+ <span
14
+ :class="[
15
+ 'flex items-center justify-center text-text-primary-brand-on-soft-bg font-bold select-none',
16
+ textSizeClass,
17
+ ]"
18
+ >
19
+ {{ text }}
20
+ </span>
21
+ </div>
22
+ </template>
23
+
24
+ <script setup lang="ts">
25
+ // Props
26
+ const props = defineProps({
27
+ type: {
28
+ type: String as PropType<StackCounterType>,
29
+ default: StackCounterType.ELLIPSIS,
30
+ validator: (value: StackCounterType) => Object.values(StackCounterType).includes(value),
31
+ },
32
+ shape: {
33
+ type: String as PropType<AvatarShape>,
34
+ default: AvatarShape.CIRCLE,
35
+ validator: (value: AvatarShape) => Object.values(AvatarShape).includes(value),
36
+ },
37
+ size: {
38
+ type: String as PropType<AvatarStackSize>,
39
+ default: AvatarStackSize.SM,
40
+ validator: (value: AvatarStackSize) => Object.values(AvatarStackSize).includes(value),
41
+ },
42
+ text: {
43
+ type: String as PropType<string>,
44
+ default: '...',
45
+ },
46
+ })
47
+
48
+ // Computed classes
49
+ const shapeClass = computed(() => {
50
+ const shapeVariant = {
51
+ [AvatarShape.CIRCLE]: 'rounded-full',
52
+ [AvatarShape.SQUARE]: 'rounded-md',
53
+ }
54
+ return shapeVariant[props.shape as AvatarShape] || 'rounded-full'
55
+ })
56
+
57
+ const sizeClass = computed(() => {
58
+ const sizeVariant = {
59
+ [AvatarStackSize.XS]: 'w-[24px] h-[24px]',
60
+ [AvatarStackSize.SM]: 'w-[32px] h-[32px]',
61
+ [AvatarStackSize.MD]: 'w-[36px] h-[36px]',
62
+ }
63
+ return sizeVariant[props.size as AvatarStackSize] || 'w-[32px] h-[32px]'
64
+ })
65
+
66
+ const textSizeClass = computed(() => {
67
+ const textSizeVariant = {
68
+ [AvatarStackSize.XS]: 'text-xs',
69
+ [AvatarStackSize.SM]: 'text-sm',
70
+ [AvatarStackSize.MD]: 'text-md',
71
+ }
72
+ return textSizeVariant[props.size as AvatarStackSize] || 'text-sm'
73
+ })
74
+ </script>
@@ -0,0 +1,221 @@
1
+ <template>
2
+ <div
3
+ :class="[
4
+ 'inline-flex items-center gap-2 font-semibold text-xs px-2 h-[24px] w-fit',
5
+ styleClass,
6
+ shapeClass,
7
+ colorClass,
8
+ borderColorClass,
9
+ isTransparent ? 'bg-transparent' : undefined,
10
+ showDot ? 'pl-3' : undefined
11
+ ]"
12
+ >
13
+ <!-- Dot -->
14
+ <span
15
+ v-if="showDot"
16
+ :class="['w-1.5 h-1.5 rounded-full', dotColorClass]"
17
+ />
18
+
19
+ <!-- Icon -->
20
+ <MdiIcon
21
+ v-if="showIcon"
22
+ :icon="icon"
23
+ size="12px"
24
+ preserveAspectRatio="xMidYMid meet"
25
+ :class="iconColorClass"
26
+ />
27
+
28
+ <!-- Text -->
29
+ <span :class="textClass">{{ text }}</span>
30
+
31
+ <!-- Close button -->
32
+ <button
33
+ v-if="closeable"
34
+ :class="[textClass, 'ml-1 hover:opacity-75 focus:outline-none']"
35
+ @click="emitClose"
36
+ >
37
+ <MdiIcon
38
+ icon="mdiClose"
39
+ size="16px"
40
+ preserveAspectRatio="xMidYMid meet"
41
+ />
42
+ </button>
43
+ </div>
44
+ </template>
45
+
46
+ <script setup lang="ts">
47
+ // Props
48
+ const props = defineProps({
49
+ styleType: {
50
+ type: String as PropType<BadgeStyle>,
51
+ default: BadgeStyle.BORDER,
52
+ validator: (value: BadgeStyle) => Object.values(BadgeStyle).includes(value),
53
+ },
54
+ shape: {
55
+ type: String as PropType<BadgeShape>,
56
+ default: BadgeShape.BADGE,
57
+ validator: (value: BadgeShape) => Object.values(BadgeShape).includes(value),
58
+ },
59
+ color: {
60
+ type: String as PropType<ColorAccent>,
61
+ default: ColorAccent.NEUTRAL,
62
+ validator: (value: ColorAccent) => Object.values(ColorAccent).includes(value),
63
+ },
64
+ showDot: {
65
+ type: Boolean as PropType<boolean>,
66
+ default: false,
67
+ },
68
+ closeable: {
69
+ type: Boolean as PropType<boolean>,
70
+ default: false,
71
+ },
72
+ isTransparent: {
73
+ type: Boolean as PropType<boolean>,
74
+ default: false,
75
+ },
76
+ showIcon: {
77
+ type: Boolean as PropType<boolean>,
78
+ default: false,
79
+ },
80
+ text: {
81
+ type: String as PropType<string>,
82
+ default: 'Badge',
83
+ },
84
+ icon: {
85
+ type: String as PropType<any>,
86
+ default: 'mdiHelp',
87
+ },
88
+ })
89
+
90
+ // Emits
91
+ const emit = defineEmits(['close'])
92
+ const emitClose = () => {
93
+ emit('close')
94
+ }
95
+
96
+ // Computed classes
97
+ const styleClass = computed(() => {
98
+ if (props.styleType === BadgeStyle.FLAT) {
99
+ return ''
100
+ }
101
+
102
+ return {
103
+ [BadgeStyle.BORDER]: 'border',
104
+ [BadgeStyle.FILLED]: 'bg-filled'
105
+ }[props.styleType] || 'border'
106
+ })
107
+
108
+ const shapeClass = computed(() => {
109
+ const shapeVariants = {
110
+ [BadgeShape.BADGE]: 'rounded',
111
+ [BadgeShape.PILL]: 'rounded-full',
112
+ }
113
+ return shapeVariants[props.shape as BadgeShape] || 'rounded'
114
+ })
115
+
116
+ const colorClass = computed(() => {
117
+ if (props.styleType === BadgeStyle.FLAT) {
118
+ const flatColorVariant: Record<ColorAccent, string> = {
119
+ [ColorAccent.NEUTRAL]: "bg-background-neutral-sublter",
120
+ [ColorAccent.SUCCESS]: "bg-background-success-subtler",
121
+ [ColorAccent.WARNING]: "bg-background-warning-subtler",
122
+ [ColorAccent.DANGER]: "bg-background-danger-subtler",
123
+ [ColorAccent.INFO]: "bg-background-info-subtler",
124
+ [ColorAccent.PRIMARY_BRAND]: "bg-background-primary-brand-soft",
125
+ [ColorAccent.SECONDARY_BRAND]: "bg-background-secondary-brand-soft",
126
+ }
127
+
128
+ return flatColorVariant[props.color as ColorAccent] || "bg-transparent text-secondary-700"
129
+ }
130
+
131
+ const colorVariant: Record<ColorAccent, string> = {
132
+ [ColorAccent.NEUTRAL]: props.styleType === BadgeStyle.FILLED
133
+ ? "bg-background-neutral-bold"
134
+ : "bg-background-neutral-sublter",
135
+ [ColorAccent.SUCCESS]: props.styleType === BadgeStyle.FILLED
136
+ ? "bg-background-success-bold"
137
+ : "bg-background-success-subtlest",
138
+ [ColorAccent.WARNING]: props.styleType === BadgeStyle.FILLED
139
+ ? "bg-background-warning-bold"
140
+ : "bg-background-warning-subtlest",
141
+ [ColorAccent.DANGER]: props.styleType === BadgeStyle.FILLED
142
+ ? "bg-background-danger-bold"
143
+ : "bg-background-danger-subtlest",
144
+ [ColorAccent.INFO]: props.styleType === BadgeStyle.FILLED
145
+ ? "bg-background-info-bold"
146
+ : "bg-background-info-subtlest",
147
+ [ColorAccent.PRIMARY_BRAND]: props.styleType === BadgeStyle.FILLED
148
+ ? "bg-background-primary-brand-default"
149
+ : "bg-background-primary-brand-soft",
150
+ [ColorAccent.SECONDARY_BRAND]: props.styleType === BadgeStyle.FILLED
151
+ ? "bg-background-secondary-brand-default"
152
+ : "bg-background-secondary-brand-soft",
153
+ }
154
+
155
+ return colorVariant[props.color as ColorAccent] || "bg-background-neutral-sublter"
156
+ })
157
+
158
+ const textClass = computed(() => {
159
+ if (props.styleType === BadgeStyle.FILLED) return "text-text-neutral-on-filled"
160
+
161
+ const textVariant: Record<ColorAccent, string> = {
162
+ [ColorAccent.NEUTRAL]: "text-text-neutral-subtle",
163
+ [ColorAccent.SUCCESS]: "text-text-success",
164
+ [ColorAccent.WARNING]: props.isTransparent ? "text-text-warning" : "text-text-warning-on-bg",
165
+ [ColorAccent.DANGER]: "text-text-danger",
166
+ [ColorAccent.INFO]: "text-text-info",
167
+ [ColorAccent.PRIMARY_BRAND]: "text-text-primary-brand-on-soft-bg",
168
+ [ColorAccent.SECONDARY_BRAND]: "text-text-secondary-brand-on-soft-bg",
169
+ }
170
+
171
+ return textVariant[props.color as ColorAccent] || "text-text-neutral-subtle"
172
+ })
173
+
174
+ const iconColorClass = computed(() => {
175
+ if (props.styleType === BadgeStyle.FILLED) return "text-text-neutral-on-filled"
176
+
177
+ const iconVariant: Record<ColorAccent, string> = {
178
+ [ColorAccent.NEUTRAL]: "text-icon-neutral-subtle",
179
+ [ColorAccent.SUCCESS]: "text-icon-success",
180
+ [ColorAccent.WARNING]: props.isTransparent ? "text-icon-warning" : "text-icon-warning-on-bg",
181
+ [ColorAccent.DANGER]: "text-icon-danger",
182
+ [ColorAccent.INFO]: "text-icon-info",
183
+ [ColorAccent.PRIMARY_BRAND]: "text-icon-primary-brand-default",
184
+ [ColorAccent.SECONDARY_BRAND]: "text-icon-secondary-brand-default",
185
+ }
186
+
187
+ return iconVariant[props.color as ColorAccent] || "text-icon-secondary"
188
+ })
189
+
190
+ const dotColorClass = computed(() => {
191
+ if (props.styleType === BadgeStyle.FILLED) return "bg-text-on-filled"
192
+
193
+ const dotVariant: Record<ColorAccent, string> = {
194
+ [ColorAccent.NEUTRAL]: "bg-icon-neutral-subtle",
195
+ [ColorAccent.SUCCESS]: "bg-icon-success",
196
+ [ColorAccent.WARNING]: "bg-icon-warning",
197
+ [ColorAccent.DANGER]: "bg-icon-danger",
198
+ [ColorAccent.INFO]: "bg-icon-info",
199
+ [ColorAccent.PRIMARY_BRAND]: "bg-icon-primary-brand-default",
200
+ [ColorAccent.SECONDARY_BRAND]: "bg-icon-secondary-brand-default",
201
+ }
202
+
203
+ return dotVariant[props.color as ColorAccent] || "bg-icon-neutral-subtle"
204
+ })
205
+
206
+ const borderColorClass = computed(() => {
207
+ if (props.styleType !== BadgeStyle.BORDER) return ""
208
+
209
+ const borderVariant: Record<ColorAccent, string> = {
210
+ [ColorAccent.NEUTRAL]: "border-border-default",
211
+ [ColorAccent.SUCCESS]: "border-border-success",
212
+ [ColorAccent.WARNING]: "border-border-warning",
213
+ [ColorAccent.DANGER]: "border-border-danger",
214
+ [ColorAccent.INFO]: "border-border-info",
215
+ [ColorAccent.PRIMARY_BRAND]: "border-border-primary-brand-default",
216
+ [ColorAccent.SECONDARY_BRAND]: "border-border-secondary-brand",
217
+ }
218
+
219
+ return borderVariant[props.color as ColorAccent] || "border-border-default"
220
+ })
221
+ </script>