@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,144 @@
1
+ <template>
2
+ <NuxtLink :to>
3
+ <component
4
+ :is="wrapperComponent"
5
+ :class="dynamicWrapperClass"
6
+ v-bind="cardProps"
7
+ >
8
+ <ContentItemImage
9
+ v-if="hasImage"
10
+ :src="imgUrl"
11
+ :alt="imgAlt"
12
+ :aspectRatio="imgAspectRatio"
13
+ :hoverEffect="imgHoverEffect"
14
+ :hoverIcon="imgHoverIcon"
15
+ :fallbackIcon="imgFallbackIcon"
16
+ :containerClass="imgContainerClass"
17
+ />
18
+
19
+ <!-- Custom content-->
20
+ <slot
21
+ v-if="$slots['content']"
22
+ name="content"
23
+ />
24
+
25
+ <!-- Content -->
26
+ <div
27
+ v-else
28
+ class="w-full flex flex-col gap-1.5"
29
+ >
30
+ <component
31
+ :is="titleTag"
32
+ :class="titleClass"
33
+ >
34
+ {{ trimText(title, titleMaxLength) }}
35
+ </component>
36
+
37
+ <p
38
+ v-if="description"
39
+ :class="descriptionClass"
40
+ >
41
+ {{ trimText(description, descriptionMaxLength) }}
42
+ </p>
43
+ </div>
44
+ </component>
45
+ </NuxtLink>
46
+ </template>
47
+
48
+ <script setup lang="ts">
49
+ // Dynamic component import
50
+ import Card from '../cards/Card.vue'
51
+
52
+ // Props
53
+ const props = defineProps({
54
+ type: {
55
+ type: String as PropType<ContentItemType>,
56
+ default: ContentItemType.CARD
57
+ },
58
+ title: {
59
+ type: String as PropType<string>,
60
+ default: 'Title',
61
+ },
62
+ titleTag: {
63
+ type: String as PropType<'h3' | 'h4' | 'h5' | 'h6'>,
64
+ default: 'h4',
65
+ validator: (value: string) => ['h3', 'h4', 'h5', 'h6'].includes(value),
66
+ },
67
+ titleClass: {
68
+ type: String as PropType<string>,
69
+ default: 'font-semibold',
70
+ },
71
+ titleMaxLength: {
72
+ type: Number as PropType<number>,
73
+ default: 72,
74
+ },
75
+ description: String as PropType<string>,
76
+ descriptionClass: {
77
+ type: String as PropType<string>,
78
+ default: 'text-sm text-text-neutral-subtle',
79
+ },
80
+ descriptionMaxLength: {
81
+ type: Number as PropType<number>,
82
+ default: 100,
83
+ },
84
+ to: String as PropType<string>,
85
+ hasImage: {
86
+ type: Boolean as PropType<boolean>,
87
+ default: true,
88
+ },
89
+ imgUrl: String as PropType<string>,
90
+ imgAlt: {
91
+ type: String as PropType<string>,
92
+ default: 'Item image'
93
+ },
94
+ imgAspectRatio: {
95
+ type: String as PropType<AspectRatio>,
96
+ default: AspectRatio.AR_16_9,
97
+ },
98
+ imgHoverEffect: {
99
+ type: String as PropType<ImageHoverEffect>,
100
+ default: ImageHoverEffect.BLUR,
101
+ validator: (value: ImageHoverEffect) => Object.values(ImageHoverEffect).includes(value),
102
+ },
103
+ imgHoverIcon: String as PropType<any>,
104
+ imgFallbackIcon: String as PropType<any>,
105
+ imgContainerClass: String as PropType<string>,
106
+ containerClass: String as PropType<string>,
107
+ hasCardShadow: {
108
+ type: Boolean as PropType<boolean>,
109
+ default: true,
110
+ },
111
+ hasCardBackgroundHover: {
112
+ type: Boolean as PropType<boolean>,
113
+ default: false,
114
+ },
115
+
116
+ })
117
+
118
+ // Computed
119
+ const dynamicWrapperClass = computed(() => {
120
+ const baseClasses = [
121
+ 'gap-4',
122
+ props.containerClass
123
+ ]
124
+
125
+ if (props.type === ContentItemType.BASIC) {
126
+ baseClasses.unshift('w-full', 'flex', 'flex-col')
127
+ }
128
+
129
+ return baseClasses
130
+ })
131
+
132
+ const wrapperComponent = computed(() => {
133
+ return props.type === ContentItemType.CARD ? Card : 'div'
134
+ })
135
+
136
+ const cardProps = computed(() => {
137
+ if (props.type !== ContentItemType.CARD) return {}
138
+
139
+ return {
140
+ hasShadow: props.hasCardShadow,
141
+ hasBackgroundHover: props.hasCardBackgroundHover,
142
+ }
143
+ })
144
+ </script>
@@ -0,0 +1,125 @@
1
+ <template>
2
+ <div
3
+ :class="[
4
+ 'w-full',
5
+ 'flex items-center justify-center',
6
+ containerClass,
7
+ ]"
8
+ >
9
+ <div
10
+ :class="[
11
+ 'w-full',
12
+ 'rounded-md',
13
+ 'relative',
14
+ 'group',
15
+ 'overflow-hidden',
16
+ aspectRatioClass,
17
+ containerClass,
18
+ ]"
19
+ >
20
+ <!-- Image or fallback -->
21
+ <img
22
+ v-if="src"
23
+ :src
24
+ :alt
25
+ :class="imageClass"
26
+ >
27
+
28
+ <div
29
+ v-else
30
+ class="w-full h-full flex items-center justify-center bg-background-neutral-subtlest"
31
+ >
32
+ <MdiIcon
33
+ :icon="fallbackIcon"
34
+ size="32"
35
+ preserveAspectRatio="xMidYMid meet"
36
+ class="text-icon-primary-brand-default"
37
+ />
38
+ </div>
39
+
40
+ <!-- Hover effect -->
41
+ <template v-if="src">
42
+ <div
43
+ v-if="hoverEffect === 'overlay'"
44
+ class="absolute inset-0 bg-background-neutral-filled-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"
45
+ />
46
+
47
+ <div
48
+ :class="[
49
+ 'absolute inset-0',
50
+ 'flex items-center justify-center',
51
+ 'opacity-0',
52
+ 'group-hover:opacity-100',
53
+ 'transition-opacity duration-300',
54
+ ]"
55
+ >
56
+ <MdiIcon
57
+ :icon="hoverIcon"
58
+ size="32"
59
+ preserveAspectRatio="xMidYMid meet"
60
+ class="text-icon-neutral-on-filled-bg"
61
+ />
62
+ </div>
63
+ </template>
64
+ </div>
65
+ </div>
66
+ </template>
67
+
68
+ <script setup lang="ts">
69
+ // Props
70
+ const props = defineProps({
71
+ src: String as PropType<string>,
72
+ alt: {
73
+ type: String,
74
+ default: 'Item image',
75
+ },
76
+ aspectRatio: {
77
+ type: String as PropType<AspectRatio>,
78
+ default: AspectRatio.AR_16_9,
79
+ },
80
+ hoverEffect: {
81
+ type: String as PropType<ImageHoverEffect>,
82
+ default: ImageHoverEffect.BLUR,
83
+ },
84
+ hoverIcon: {
85
+ type: String as PropType<any>,
86
+ default: 'mdiEyeOutline',
87
+ },
88
+ fallbackIcon: {
89
+ type: String as PropType<any>,
90
+ default: 'mdiImageOffOutline',
91
+ },
92
+ containerClass: String as PropType<string>,
93
+ })
94
+
95
+ // Computed classes
96
+ const aspectRatioClass = computed(() => {
97
+ const variant: Record<AspectRatio, string> = {
98
+ [AspectRatio.AR_1_1]: 'aspect-[1/1]',
99
+ [AspectRatio.AR_4_3]: 'aspect-[4/3]',
100
+ [AspectRatio.AR_3_2]: 'aspect-[3/2]',
101
+ [AspectRatio.AR_16_9]: 'aspect-[16/9]',
102
+ [AspectRatio.AR_3_4]: 'aspect-[3/4]',
103
+ [AspectRatio.AR_4_5]: 'aspect-[4/5]',
104
+ [AspectRatio.AR_2_3]: 'aspect-[2/3]',
105
+ }
106
+
107
+ return variant[props.aspectRatio as AspectRatio] || 'aspect-[16/9]'
108
+ })
109
+
110
+ const imageClass = computed(() => {
111
+ if (props.hoverEffect === ImageHoverEffect.ZOOM_IN) {
112
+ return 'w-full h-full object-cover transition-transform duration-300 scale-100 group-hover:scale-110'
113
+ }
114
+
115
+ if (props.hoverEffect === ImageHoverEffect.ZOOM_OUT) {
116
+ return 'w-full h-full object-cover transition-transform duration-300 scale-110 group-hover:scale-100'
117
+ }
118
+
119
+ if (props.hoverEffect === ImageHoverEffect.BLUR) {
120
+ return 'w-full h-full object-cover transition duration-300 group-hover:blur-xs'
121
+ }
122
+
123
+ return 'w-full h-full object-cover'
124
+ })
125
+ </script>
@@ -0,0 +1,35 @@
1
+ <template>
2
+ <hr
3
+ :class="[
4
+ orientation === Orientation.VERTICAL
5
+ ? [
6
+ 'h-full',
7
+ 'min-h-[16px]',
8
+ 'w-px',
9
+ 'border-none',
10
+ 'bg-border-default',
11
+ 'mx-2',
12
+ ]
13
+ : [
14
+ 'w-full',
15
+ 'border-xs',
16
+ 'border-border-default',
17
+ ],
18
+ hideOnMobile && 'hidden md:block',
19
+ ]"
20
+ >
21
+ </template>
22
+
23
+ <script setup lang="ts">
24
+ defineProps({
25
+ orientation: {
26
+ type: String as PropType<Orientation>,
27
+ default: Orientation.HORIZONTAL,
28
+ validator: (value: Orientation) => Object.values(Orientation).includes(value),
29
+ },
30
+ hideOnMobile: {
31
+ type: Boolean as PropType<boolean>,
32
+ default: false,
33
+ },
34
+ })
35
+ </script>
@@ -0,0 +1,58 @@
1
+ <template>
2
+ <div class="w-full flex items-center gap-4">
3
+ <Divider v-if="align === Align.CENTER"/>
4
+ <span
5
+ :class="[
6
+ 'font-semibold',
7
+ 'text-nowrap',
8
+ textSizeClass,
9
+ textColorClass
10
+ ]"
11
+ >
12
+ {{ text }}
13
+ </span>
14
+ <Divider />
15
+ </div>
16
+ </template>
17
+ <script setup lang="ts">
18
+ // Props
19
+ const props = defineProps({
20
+ text: {
21
+ type: String as PropType<string>,
22
+ default: 'Text',
23
+ },
24
+ styleType: {
25
+ type: String as PropType<DividerStyle>,
26
+ default: DividerStyle.NEUTRAL,
27
+ validator: (value: DividerStyle) => Object.values(DividerStyle).includes(value),
28
+ },
29
+ size: {
30
+ type: String as PropType<DividerSize>,
31
+ default: DividerSize.XS,
32
+ validator: (value: DividerSize) => Object.values(DividerSize).includes(value),
33
+ },
34
+ align: {
35
+ type: String as PropType<Align.LEFT | Align.CENTER>,
36
+ default: Align.CENTER,
37
+ validator: (value: Align) => [Align.LEFT, Align.CENTER].includes(value),
38
+ },
39
+ })
40
+
41
+ // Computed classes
42
+ const textColorClass = computed(() => {
43
+ const variant = {
44
+ [DividerStyle.NEUTRAL]: 'text-text-neutral-subtle',
45
+ [DividerStyle.PRIMARY_BRAND]: 'text-text-primary-brand-default',
46
+ [DividerStyle.SECONDARY_BRAND]: 'text-text-secondary-brand-default',
47
+ }
48
+ return variant[props.styleType as DividerStyle] || 'text-text-neutral-subtle'
49
+ })
50
+
51
+ const textSizeClass = computed(() => {
52
+ const variant = {
53
+ [DividerSize.XS]: 'text-xs',
54
+ [DividerSize.SM]: 'text-sm',
55
+ }
56
+ return variant[props.size as DividerSize] || 'text-xs'
57
+ })
58
+ </script>
@@ -0,0 +1,207 @@
1
+ <template>
2
+ <div
3
+ ref="dropdownContainer"
4
+ data-test="dropdown-container"
5
+ :class="[isRelative && 'relative']"
6
+ >
7
+ <div
8
+ v-if="isOpen"
9
+ v-bind="$attrs"
10
+ :class="[
11
+ 'bg-background-surface',
12
+ 'py-1',
13
+ 'rounded',
14
+ hasShadow && 'shadow-lg',
15
+ 'w-full',
16
+ 'flex flex-col',
17
+ 'z-50',
18
+ hasBorder && 'border border-border-default',
19
+ positionClass ? positionClass : dropdownPositionClass,
20
+ dropdownClass,
21
+ ]"
22
+ :style="!positionClass && positionOffsetStyle"
23
+ >
24
+ <slot
25
+ v-if="$slots['items']"
26
+ name="items"
27
+ :onClose="toggle"
28
+ />
29
+ <template v-else-if="items?.length && !$slots['items']">
30
+ <DropdownMenuItem
31
+ v-for="(item, index) in items" :key="index"
32
+ :actionType="item.actionType"
33
+ :text="item.text"
34
+ :icon="item.icon"
35
+ :size="item.size"
36
+ :type="item.type"
37
+ :userDisplayName="item.userDisplayName"
38
+ :userProfileImg="item.userProfileImg"
39
+ :imgUrl="item.imgUrl"
40
+ :alt="item.alt"
41
+ :helpText="item.helpText"
42
+ :to="item.to"
43
+ :isExternal="item.isExternal"
44
+ :exportData="item.exportData"
45
+ :exportFields="item.exportFields"
46
+ :exportType="item.exportType"
47
+ :exportFileName="item.exportFileName"
48
+ :hasSeparator="item.hasSeparator"
49
+ @click="handleClick(item.callback)"
50
+ />
51
+ </template>
52
+ </div>
53
+ <!-- Activator Slot -->
54
+ <slot
55
+ name="activator"
56
+ :onClick="toggle"
57
+ :isOpen
58
+ />
59
+ </div>
60
+ </template>
61
+ <script setup lang="ts">
62
+ // Component options
63
+ defineOptions({
64
+ inheritAttrs: false, // Prevents Vue from automatically applying attributes incorrectly
65
+ })
66
+
67
+ // Props
68
+ const props = defineProps({
69
+ items: Array as PropType<DropdownMenuItem[]>,
70
+ position: {
71
+ type: String as PropType<DropdownPosition>,
72
+ default: DropdownPosition.BOTTOM_RIGHT,
73
+ validator: (value: DropdownPosition) => Object.values(DropdownPosition).includes(value),
74
+ },
75
+ hasShadow: {
76
+ type: Boolean as PropType<boolean>,
77
+ default: true,
78
+ },
79
+ hasBorder: {
80
+ type: Boolean as PropType<boolean>,
81
+ default: true,
82
+ },
83
+ positionXOffset: {
84
+ type: [Number, String] as PropType<number | string>,
85
+ default: 0,
86
+ },
87
+ positionYOffset: {
88
+ type: [Number, String] as PropType<number | string>,
89
+ default: 0,
90
+ },
91
+ positionClass: String as PropType<string>,
92
+ dropdownClass: String as PropType<string>,
93
+ isRelative: {
94
+ type: Boolean as PropType<boolean>,
95
+ default: true,
96
+ },
97
+ })
98
+
99
+ // Ref
100
+ const dropdownContainer = ref(null)
101
+
102
+ // Composables
103
+ const [isOpen, toggle] = useToggle(false)
104
+
105
+ onClickOutside(dropdownContainer, () => {
106
+ isOpen.value = false
107
+ })
108
+
109
+ // Handlers
110
+ const handleClick = (callback?: () => void) => {
111
+ if(callback) {
112
+ callback()
113
+ }
114
+ toggle()
115
+ }
116
+
117
+ // Computed
118
+ const dropdownPositionClass = computed(() => {
119
+ const variant = {
120
+ [DropdownPosition.LEFT_TOP]: 'absolute right-full top-0',
121
+ [DropdownPosition.LEFT_BOTTOM]: 'absolute right-full bottom-0',
122
+ [DropdownPosition.RIGHT_TOP]: 'absolute left-full top-0',
123
+ [DropdownPosition.RIGHT_BOTTOM]: 'absolute left-full bottom-0',
124
+ [DropdownPosition.TOP_LEFT]: 'absolute left-0 bottom-full',
125
+ [DropdownPosition.TOP_RIGHT]: 'absolute right-0 bottom-full',
126
+ [DropdownPosition.BOTTOM_LEFT]: 'absolute left-0 top-full',
127
+ [DropdownPosition.BOTTOM_RIGHT]: 'absolute right-0 top-full',
128
+ }
129
+
130
+ return variant[props.position as DropdownPosition] || 'absolute right-0 top-full'
131
+ })
132
+
133
+ const positionOffsetStyle = computed(() => {
134
+ const normalizeOffset = (value: number | string): string => {
135
+ if (typeof value === 'number') return `${value}px`
136
+ if (/\d+(px|rem|em|%)$/.test(value)) return value
137
+ return `${value}px`
138
+ }
139
+
140
+ const x = props.positionXOffset
141
+ const y = props.positionYOffset
142
+ const position = props.position as DropdownPosition
143
+
144
+ const style: Record<string, string> = {}
145
+
146
+ // Vertical alignment
147
+ if (
148
+ position === DropdownPosition.TOP_LEFT ||
149
+ position === DropdownPosition.TOP_RIGHT
150
+ ) {
151
+ style.marginBottom = normalizeOffset(y)
152
+ }
153
+
154
+ if (
155
+ position === DropdownPosition.BOTTOM_LEFT ||
156
+ position === DropdownPosition.BOTTOM_RIGHT
157
+ ) {
158
+ style.marginTop = normalizeOffset(y)
159
+ }
160
+
161
+ if (
162
+ position === DropdownPosition.LEFT_TOP ||
163
+ position === DropdownPosition.RIGHT_TOP
164
+ ) {
165
+ style.marginTop = normalizeOffset(y)
166
+ }
167
+
168
+ if (
169
+ position === DropdownPosition.LEFT_BOTTOM ||
170
+ position === DropdownPosition.RIGHT_BOTTOM
171
+ ) {
172
+ style.marginBottom = normalizeOffset(y)
173
+ }
174
+
175
+ // Horizontal alignment
176
+ if (
177
+ position === DropdownPosition.TOP_LEFT ||
178
+ position === DropdownPosition.BOTTOM_LEFT
179
+ ) {
180
+ style.marginLeft = normalizeOffset(x)
181
+ }
182
+
183
+ if (
184
+ position === DropdownPosition.TOP_RIGHT ||
185
+ position === DropdownPosition.BOTTOM_RIGHT
186
+ ) {
187
+ style.marginRight = normalizeOffset(x)
188
+ }
189
+
190
+ if (
191
+ position === DropdownPosition.LEFT_TOP ||
192
+ position === DropdownPosition.LEFT_BOTTOM
193
+ ) {
194
+ style.marginRight = normalizeOffset(x)
195
+ }
196
+
197
+ if (
198
+ position === DropdownPosition.RIGHT_TOP ||
199
+ position === DropdownPosition.RIGHT_BOTTOM
200
+ ) {
201
+ style.marginLeft = normalizeOffset(x)
202
+ }
203
+
204
+ return style
205
+ })
206
+
207
+ </script>
@@ -0,0 +1,11 @@
1
+ <template>
2
+ <div
3
+ :class="[
4
+ 'flex flex-col justify-center gap-3',
5
+ 'px-3 py-2',
6
+ 'w-full',
7
+ ]"
8
+ >
9
+ <slot />
10
+ </div>
11
+ </template>