@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,122 @@
1
+ <template>
2
+ <Teleport to="body">
3
+ <div
4
+ v-if="modelValue"
5
+ :id
6
+ class="fixed inset-0 z-[9999] bg-background-overlay backdrop-blur-sm overflow-y-auto"
7
+
8
+ >
9
+ <div
10
+ class="modal-container min-h-full flex w-full items-center justify-center p-4"
11
+ @click.self="closeModalOnClickOutside"
12
+ >
13
+ <div
14
+ :class="[
15
+ 'bg-background-surface rounded-lg shadow-xl',
16
+ 'relative w-full my-8',
17
+ 'overflow-visible',
18
+ cardClasses,
19
+ ]"
20
+ >
21
+ <ActionIconButton
22
+ v-if="hasCornerCloseButton"
23
+ :styleType="ButtonStyleType.NEUTRAL_TRANSPARENT"
24
+ :size="ButtonSize.MD"
25
+ icon="mdiClose"
26
+ class="absolute top-4 right-4 z-10"
27
+ @click="closeModal"
28
+ />
29
+
30
+ <div class="p-4 md:p-6">
31
+ <slot />
32
+ </div>
33
+ </div>
34
+ </div>
35
+ </div>
36
+ </Teleport>
37
+ </template>
38
+
39
+ <script setup lang="ts">
40
+ // Props
41
+ const props = defineProps({
42
+ modelValue: {
43
+ type: Boolean as PropType<boolean>,
44
+ default: false,
45
+ },
46
+ cardClasses: {
47
+ type: String as PropType<string>,
48
+ default: 'max-w-[600px]',
49
+ },
50
+ closeOnClickOutside: {
51
+ type: Boolean as PropType<boolean>,
52
+ default: false,
53
+ },
54
+ hasCornerCloseButton: {
55
+ type: Boolean as PropType<boolean>,
56
+ default: true,
57
+ },
58
+ id: String as PropType<string>,
59
+ })
60
+
61
+ // Emits
62
+ const emit = defineEmits(['update:modelValue', 'close'])
63
+
64
+ // Watch for `modelValue` changes
65
+ watch(
66
+ () => props.modelValue,
67
+ newValue => {
68
+ if (newValue) {
69
+ addEscListener() // Add Esc key listener
70
+ } else {
71
+ removeEscListener() // Remove Esc key listener
72
+ }
73
+ }
74
+ )
75
+
76
+ // Handlers
77
+ const closeModal = () => {
78
+ emit('update:modelValue', false)
79
+ emit('close') // Useful for triggering actions on close
80
+ }
81
+
82
+ const closeModalOnClickOutside = () => {
83
+ if (props.closeOnClickOutside) {
84
+ closeModal()
85
+ }
86
+ }
87
+
88
+ const handleEscKey = (event: KeyboardEvent) => {
89
+ if (props.closeOnClickOutside && event.key === 'Escape') {
90
+ closeModal()
91
+ }
92
+ }
93
+
94
+ // Event listeners
95
+ const addEscListener = () => {
96
+ window.addEventListener('keydown', handleEscKey)
97
+ }
98
+
99
+ const removeEscListener = () => {
100
+ window.removeEventListener('keydown', handleEscKey)
101
+ }
102
+
103
+ // Cleanup on component unmount
104
+ onUnmounted(() => {
105
+ removeEscListener()
106
+ })
107
+
108
+ // Locks and unlocks the background scroll while modal is open
109
+ useHead(() => {
110
+ return props.modelValue
111
+ ? {
112
+ bodyAttrs: {
113
+ class: 'modal-open',
114
+ },
115
+ }
116
+ : {
117
+ bodyAttrs: {
118
+ class: '',
119
+ },
120
+ }
121
+ })
122
+ </script>
@@ -0,0 +1,19 @@
1
+ <template>
2
+ <div
3
+ :class="[
4
+ 'flex flex-col gap-2',
5
+ centered && 'text-center'
6
+ ]"
7
+ >
8
+ <slot />
9
+ </div>
10
+ </template>
11
+ <script setup lang="ts">
12
+ // Props
13
+ defineProps({
14
+ centered: {
15
+ type: Boolean as PropType<boolean>,
16
+ default: false,
17
+ },
18
+ })
19
+ </script>
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <div class="flex flex-col gap-1">
3
+ <slot />
4
+ </div>
5
+ </template>
@@ -0,0 +1,14 @@
1
+ <template>
2
+ <h3 class="font-semibold text-text-neutral-subtle">
3
+ {{ title }}
4
+ </h3>
5
+ </template>
6
+ <script setup lang="ts">
7
+ // Props
8
+ defineProps({
9
+ title: {
10
+ type: String as PropType<string>,
11
+ default: 'Modal subtitle',
12
+ },
13
+ })
14
+ </script>
@@ -0,0 +1,14 @@
1
+ <template>
2
+ <h2 class="font-bold">
3
+ {{ title }}
4
+ </h2>
5
+ </template>
6
+ <script setup lang="ts">
7
+ // Props
8
+ defineProps({
9
+ title: {
10
+ type: String as PropType<string>,
11
+ default: 'Modal title',
12
+ },
13
+ })
14
+ </script>
@@ -0,0 +1,90 @@
1
+ <template>
2
+ <ModalDialog
3
+ :modelValue
4
+ :closeOnClickOutside
5
+ :hasCornerCloseButton
6
+ :cardClasses
7
+ @update:modelValue="updateModelValue"
8
+ >
9
+ <ModalContent class="!gap-6 items-center">
10
+ <ContainedIcon
11
+ :color="ColorAccent.SUCCESS"
12
+ :size="IconContainerSize.XL"
13
+ :icon
14
+ />
15
+
16
+ <ModalHeaderGroup centered>
17
+ <ModalTitle :title />
18
+ <ModalDescription v-if="description">
19
+ {{ description }}
20
+ </ModalDescription>
21
+ </ModalHeaderGroup>
22
+
23
+ <slot />
24
+
25
+ <ModalActions v-if="showModalActions">
26
+ <ActionButton
27
+ :styleType="ButtonStyleType.PRIMARY_BRAND_FILLED"
28
+ :text="buttonText"
29
+ class="w-full"
30
+ @click="handleClose"
31
+ />
32
+ </ModalActions>
33
+ </ModalContent>
34
+ </ModalDialog>
35
+ </template>
36
+
37
+ <script setup lang="ts">
38
+ // Props
39
+ defineProps({
40
+ modelValue: {
41
+ type: Boolean as PropType<boolean>,
42
+ default: false,
43
+ },
44
+ icon: {
45
+ type: String as PropType<any>,
46
+ default: 'mdiCheckBold',
47
+ },
48
+ title: {
49
+ type: String as PropType<string>,
50
+ default: 'Modal title',
51
+ },
52
+ description: String as PropType<string>,
53
+ buttonText: {
54
+ type: String as PropType<string>,
55
+ default: 'Close',
56
+ },
57
+ cardClasses: {
58
+ type: String as PropType<string>,
59
+ default: 'max-w-[400px]',
60
+ },
61
+ closeOnClickOutside: {
62
+ type: Boolean as PropType<boolean>,
63
+ default: false,
64
+ },
65
+ hasCornerCloseButton: {
66
+ type: Boolean as PropType<boolean>,
67
+ default: false,
68
+ },
69
+ showModalActions: {
70
+ type: Boolean as PropType<boolean>,
71
+ default: true,
72
+ },
73
+ })
74
+
75
+ // Emits
76
+ const emit = defineEmits(['update:modelValue', 'close'])
77
+
78
+ // Handlers
79
+ const updateModelValue = (value: boolean) => {
80
+ emit('update:modelValue', value)
81
+ }
82
+
83
+ const handleClose = () => {
84
+ // Emit the model update to close the modal
85
+ updateModelValue(false)
86
+
87
+ // Emit the custom "close" event for additional actions
88
+ emit('close')
89
+ }
90
+ </script>
@@ -0,0 +1,46 @@
1
+ <template>
2
+ <NuxtLink
3
+ v-if="redirectToHome"
4
+ :to
5
+ >
6
+ <SVGImage
7
+ :src
8
+ :alt="ariaLabel"
9
+ :class="logoClass"
10
+ />
11
+ </NuxtLink>
12
+ <div
13
+ v-else
14
+ :class="logoClass"
15
+ :aria-label="ariaLabel"
16
+ v-html="src"
17
+ />
18
+ </template>
19
+ <script setup lang="ts">
20
+ // Imports
21
+ import logo from '@/public/images/logo/air-ui-logo-color.svg?raw'
22
+
23
+ // Props
24
+ defineProps({
25
+ src: {
26
+ type: String as PropType<string>,
27
+ default: logo
28
+ },
29
+ ariaLabel: {
30
+ type: String as PropType<string>,
31
+ default: 'App logo'
32
+ },
33
+ redirectToHome: {
34
+ type: Boolean as PropType<boolean>,
35
+ default: true
36
+ },
37
+ to: {
38
+ type: String as PropType<string>,
39
+ default: '/',
40
+ },
41
+ logoClass: {
42
+ type: String as PropType<string>,
43
+ default: 'max-w-[118px]',
44
+ }
45
+ })
46
+ </script>
@@ -0,0 +1,44 @@
1
+ <template>
2
+ <div
3
+ class="responsive-svg w-full"
4
+ :aria-label="alt"
5
+ :class="colorClass"
6
+ v-html="processedSvg"
7
+ />
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+ const props = defineProps({
12
+ src: {
13
+ type: String as PropType<string>,
14
+ required: true
15
+ },
16
+ alt: {
17
+ type: String as PropType<string>,
18
+ default: 'Vector image'
19
+ },
20
+ color: {
21
+ type: String as PropType<string>,
22
+ default: ''
23
+ },
24
+ })
25
+
26
+ // Dynamically process the SVG
27
+ const processedSvg = computed(() => {
28
+ let svg = props.src
29
+ .replace(/width="\d+"/g, '')
30
+ .replace(/height="\d+"/g, '')
31
+
32
+ // Only replace fills/strokes if a color was provided
33
+ if (props.color) {
34
+ svg = svg
35
+ .replace(/fill="(.*?)"/g, 'fill="currentColor"')
36
+ .replace(/stroke="(.*?)"/g, 'stroke="currentColor"')
37
+ }
38
+
39
+ return svg
40
+ })
41
+
42
+ // Apply color class if defined
43
+ const colorClass = computed(() => props.color || '')
44
+ </script>
@@ -0,0 +1,112 @@
1
+ <template>
2
+ <NuxtLink
3
+ :to="!disabled ? to : undefined"
4
+ :target="isExternal ? '_blank' : '_self'"
5
+ :rel="isExternal ? 'noopener noreferrer' : undefined"
6
+ :external="isExternal"
7
+ class="inline-flex items-center justify-center w-fit"
8
+ >
9
+ <div
10
+ :class="[
11
+ 'group flex items-center justify-center',
12
+ 'text-text-primary-brand-default',
13
+ !props.disabled ? 'hover:text-text-primary-brand-hover' : 'opacity-disabled cursor-not-allowed',
14
+ gapClass,
15
+ ]">
16
+ <!-- Left icon -->
17
+ <MdiIcon
18
+ v-if="iconPosition === IconPosition.LEFT"
19
+ :icon="icon"
20
+ :size="iconSizeClass"
21
+ preserveAspectRatio="xMidYMid meet"
22
+ />
23
+ <span :class="[ textSizeClass, textClass ]">
24
+ {{ text }}
25
+ </span>
26
+ <!-- Right icon -->
27
+ <MdiIcon
28
+ v-if="iconPosition === IconPosition.RIGHT"
29
+ :icon="icon"
30
+ :size="iconSizeClass"
31
+ preserveAspectRatio="xMidYMid meet"
32
+ />
33
+ </div>
34
+ </NuxtLink>
35
+ </template>
36
+
37
+ <script setup lang="ts">
38
+ // Props
39
+ const props = defineProps({
40
+ text: {
41
+ type: String as PropType<string>,
42
+ default: 'Link text'
43
+ },
44
+ size: {
45
+ type: String as PropType<NavLinkSize>,
46
+ default: NavLinkSize.SM,
47
+ validator: (value: NavLinkSize) => Object.values(NavLinkSize).includes(value),
48
+ },
49
+ icon: {
50
+ type: String as PropType<any>,
51
+ default: "mdiArrowRightThin"
52
+ },
53
+ iconPosition: {
54
+ type: String as PropType<IconPosition>,
55
+ default: IconPosition.NONE,
56
+ validator: (value: IconPosition) => Object.values(IconPosition).includes(value),
57
+ },
58
+ disabled: {
59
+ type: Boolean as PropType<boolean>,
60
+ default: false,
61
+ },
62
+ to: {
63
+ type: String as PropType<string>,
64
+ default: '/'
65
+ },
66
+ isExternal: {
67
+ type: Boolean as PropType<boolean>,
68
+ default: false
69
+ },
70
+ textClass: {
71
+ type: String as PropType<string>,
72
+ default: 'font-semibold'
73
+ },
74
+ })
75
+
76
+ // Computed classes
77
+ const iconSizeClass = computed(() => {
78
+ const sizeVariant = {
79
+ [NavLinkSize.XS]: '16px',
80
+ [NavLinkSize.SM]: '16px',
81
+ [NavLinkSize.MD]: '16px',
82
+ [NavLinkSize.LG]: '20px',
83
+ [NavLinkSize.XL]: '24px',
84
+ [NavLinkSize.XXL]: '28px',
85
+ }
86
+ return sizeVariant[props.size as NavLinkSize] || '20px'
87
+ })
88
+
89
+ const textSizeClass = computed(() => {
90
+ const sizeVariant = {
91
+ [NavLinkSize.XS]: 'text-xs',
92
+ [NavLinkSize.SM]: 'text-sm',
93
+ [NavLinkSize.MD]: 'text-md',
94
+ [NavLinkSize.LG]: 'text-lg',
95
+ [NavLinkSize.XL]: 'text-xl',
96
+ [NavLinkSize.XXL]: 'text-2xl',
97
+ }
98
+ return sizeVariant[props.size as NavLinkSize] || 'text-sm'
99
+ })
100
+
101
+ const gapClass = computed(() => {
102
+ const sizeVariant = {
103
+ [NavLinkSize.XS]: 'gap-1',
104
+ [NavLinkSize.SM]: 'gap-1.5',
105
+ [NavLinkSize.MD]: 'gap-2',
106
+ [NavLinkSize.LG]: 'gap-2',
107
+ [NavLinkSize.XL]: 'gap-2',
108
+ [NavLinkSize.XXL]: 'gap-2',
109
+ }
110
+ return sizeVariant[props.size as NavLinkSize] || 'gap-2'
111
+ })
112
+ </script>
@@ -0,0 +1,91 @@
1
+ <template>
2
+ <div
3
+ :class="[
4
+ orientation === Orientation.HORIZONTAL ? 'flex flex-col gap-2' : 'flex gap-6 flex-wrap',
5
+ ]"
6
+ >
7
+ <div>
8
+ <p
9
+ v-if="title"
10
+ class="text-sm font-semibold text-theme-neutral-50 mb-4"
11
+ >
12
+ {{ title }}
13
+ </p>
14
+
15
+ <!-- Solo SVGs -->
16
+ <div
17
+ v-if="onlySvgs"
18
+ class="flex gap-3 flex-wrap"
19
+ >
20
+ <NuxtLink
21
+ v-for="(item, index) in items"
22
+ :key="index"
23
+ :to="item.path"
24
+ class="inline-block"
25
+ target="_blank"
26
+ >
27
+ <SVGImage
28
+ v-if="item.svg"
29
+ :src="item.svg"
30
+ alt="icon"
31
+ class="max-w-[20px] filter invert"
32
+ />
33
+ </NuxtLink>
34
+ </div>
35
+
36
+ <!-- Texto / Iconos -->
37
+ <div
38
+ v-else
39
+ :class="[
40
+ orientation === Orientation.VERTICAL
41
+ ? 'flex flex-col gap-4'
42
+ : 'flex gap-6 flex-wrap',
43
+ ]"
44
+ >
45
+ <NuxtLink
46
+ v-for="(item, index) in items"
47
+ :key="index"
48
+ :to="item.path"
49
+ class="flex items-center gap-2 text-sm text-theme-neutral-50 hover:underline"
50
+ >
51
+ <MdiIcon
52
+ v-if="item.icon"
53
+ :icon="item.icon"
54
+ size="20"
55
+ preserveAspectRatio="xMidYMid meet"
56
+ />
57
+ <span v-if="item.text">{{ item.text }}</span>
58
+ </NuxtLink>
59
+ </div>
60
+ </div>
61
+ </div>
62
+ </template>
63
+
64
+ <script setup lang="ts">
65
+ import { computed } from 'vue'
66
+ import { NuxtLink } from '#components'
67
+
68
+ type NavItem = {
69
+ path: string
70
+ text?: string
71
+ icon?: string
72
+ svg?: string
73
+ }
74
+
75
+ const props = defineProps({
76
+ title: {
77
+ type: String,
78
+ default: '',
79
+ },
80
+ items: {
81
+ type: Array as PropType<NavItem[]>,
82
+ required: true,
83
+ },
84
+ orientation: {
85
+ type: String as PropType<Orientation>,
86
+ default: Orientation.VERTICAL,
87
+ },
88
+ })
89
+
90
+ const onlySvgs = computed(() => props.items.every(item => item.svg && !item.text && !item.icon))
91
+ </script>
@@ -0,0 +1,36 @@
1
+ <template>
2
+ <nav class="nav-menu flex gap-8 px-6">
3
+ <NavMenuItem
4
+ v-for="item in menuItems" :key="item.text"
5
+ :text="item.text"
6
+ :to="item.to"
7
+ :detectActive
8
+ />
9
+ </nav>
10
+ </template>
11
+ <script setup lang="ts">
12
+ // Props
13
+ defineProps({
14
+ menuItems: {
15
+ type: Array as PropType<MenuItem[]>,
16
+ default: () => [
17
+ {
18
+ 'text': 'Menu item 1',
19
+ 'to': '/',
20
+ },
21
+ {
22
+ 'text': 'Menu item 2',
23
+ 'to': '/',
24
+ },
25
+ {
26
+ 'text': 'Menu item 3',
27
+ 'to': '/',
28
+ },
29
+ ],
30
+ },
31
+ detectActive: {
32
+ type: Boolean as PropType<boolean>,
33
+ default: true,
34
+ },
35
+ })
36
+ </script>
@@ -0,0 +1,44 @@
1
+ <template>
2
+ <NuxtLink :to="to">
3
+ <span
4
+ :class="[
5
+ 'font-medium',
6
+ 'transition-colors',
7
+ 'duration-200',
8
+ activeClass
9
+ ]"
10
+ >
11
+ {{ text }}
12
+ </span>
13
+ </NuxtLink>
14
+ </template>
15
+
16
+ <script setup lang="ts">
17
+ // Props
18
+ const props = defineProps({
19
+ text: {
20
+ type: String as PropType<string>,
21
+ default: 'Menu item',
22
+ },
23
+ to: {
24
+ type: String as PropType<string>,
25
+ default: '/',
26
+ },
27
+ detectActive: {
28
+ type: Boolean,
29
+ default: true,
30
+ },
31
+ })
32
+
33
+ // Composables
34
+ const route = useRoute()
35
+
36
+ // Computed — return the class directly
37
+ const activeClass = computed(() => {
38
+ const isActive = props.detectActive && route.path === props.to
39
+
40
+ return isActive
41
+ ? 'text-text-primary-brand-active'
42
+ : 'hover:text-text-primary-brand-active text-text-default'
43
+ })
44
+ </script>