@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,274 @@
1
+ <template>
2
+ <div
3
+ :class="[
4
+ 'flex',
5
+ 'justify-between',
6
+ 'items-center',
7
+ 'gap-3',
8
+ 'flex-col-reverse',
9
+ 'lg:flex-row',
10
+ 'w-full'
11
+ ]"
12
+ >
13
+ <div
14
+ :class="[
15
+ 'flex',
16
+ 'items-center',
17
+ 'justify-between',
18
+ 'gap-4',
19
+ 'w-full'
20
+ ]"
21
+ >
22
+ <p class="text-text-neutral-subtle text-xs xs:text-sm">
23
+ {{ renderedResultsText }}
24
+ </p>
25
+ <RowsPerPage
26
+ v-model="itemsPerPage"
27
+ :rowsLabel="rowsPerPageLabel"
28
+ :rowsOptions="rowsPerPageOptions"
29
+ class="flex lg:hidden"
30
+ /> <!-- Mobile position -->
31
+ </div>
32
+
33
+ <div
34
+ v-if="totalItems > 5"
35
+ :class="[
36
+ 'flex',
37
+ 'gap-6',
38
+ 'flex-col',
39
+ 'w-full',
40
+ 'lg:flex-row',
41
+ 'lg:w-auto'
42
+ ]"
43
+ >
44
+ <RowsPerPage
45
+ v-if="showRowsPerPage"
46
+ v-model="itemsPerPage"
47
+ :rowsLabel="rowsPerPageLabel"
48
+ :rowsOptions="rowsPerPageOptions"
49
+ class="hidden lg:flex"
50
+ />
51
+ <nav
52
+ v-if="totalItems > itemsPerPage"
53
+ :class="[
54
+ 'flex',
55
+ styleType === ButtonPaginationStyle.BUTTON && 'border border-border-default rounded',
56
+ 'overflow-hidden'
57
+ ]"
58
+ >
59
+ <!-- Previous Button -->
60
+ <PaginationButton
61
+ :styleType
62
+ :disabled="modelValue === 1"
63
+ @click="goToPreviousPage"
64
+ >
65
+ <MdiIcon
66
+ icon="mdiChevronLeft"
67
+ size="20px"
68
+ preserveAspectRatio="xMidYMid meet"
69
+ class="text-icon-neutral-subtle"
70
+ />
71
+ </PaginationButton>
72
+
73
+ <!-- Pagination Button Loop -->
74
+ <PaginationButton
75
+ v-for="page in visiblePages"
76
+ :key="page"
77
+ :styleType
78
+ :class="[
79
+ styleType === ButtonPaginationStyle.BUTTON && 'border border-border-default',
80
+ page === modelValue ? [
81
+ styleType === ButtonPaginationStyle.BUTTON && 'bg-background-primary-brand-active',
82
+ 'text-text-neutral-on-filled',
83
+ 'font-semibold',
84
+ styleType === ButtonPaginationStyle.OVERLINE && 'border-t-2',
85
+ styleType === ButtonPaginationStyle.BUTTON ?
86
+ 'hover:bg-background-primary-brand-hover'
87
+ : 'hover:!bg-background-primary-brand-soft',
88
+ '!border-background-primary-brand-active'
89
+ ] : [
90
+ 'hover:bg-background-neutral-subtle',
91
+ 'hover:text-text-default'
92
+ ],
93
+ page === '...' ? 'cursor-default hover:!cursor-not-allowed hover:!bg-transparent' : 'cursor-pointer',
94
+ ]"
95
+ @click="() => handlePageClick(page)"
96
+ >
97
+ <span
98
+ v-if="page !== '...'"
99
+ :class="[
100
+ 'transition-colors duration-200',
101
+ page === modelValue ?
102
+ (
103
+ styleType === ButtonPaginationStyle.BUTTON
104
+ ? 'text-text-on-filled'
105
+ : 'text-text-primary-brand-active'
106
+ )
107
+ : (
108
+ styleType === ButtonPaginationStyle.BUTTON
109
+ ? 'text-text-default'
110
+ : 'text-text-neutral-inactive'
111
+ )
112
+ ]"
113
+ >
114
+ {{ page }}
115
+ </span>
116
+ <span v-else class="text-text-neutral-subtle opacity-50">...</span>
117
+ </PaginationButton>
118
+
119
+ <!-- Next Button -->
120
+ <PaginationButton
121
+ :styleType
122
+ :disabled="modelValue === totalPages"
123
+ @click="goToNextPage"
124
+ >
125
+ <MdiIcon
126
+ icon="mdiChevronRight"
127
+ size="20px"
128
+ preserveAspectRatio="xMidYMid meet"
129
+ class="text-icon-neutral-subtle"
130
+ />
131
+ </PaginationButton>
132
+ </nav>
133
+ </div>
134
+ </div>
135
+ </template>
136
+
137
+ <script setup lang="ts">
138
+ // Props
139
+ const props = defineProps({
140
+ styleType: {
141
+ type: String as PropType<ButtonPaginationStyle>,
142
+ default: ButtonPaginationStyle.BUTTON,
143
+ validator: (value: ButtonPaginationStyle) => Object.values(ButtonPaginationStyle).includes(value),
144
+ },
145
+ modelValue: {
146
+ type: Number as PropType<number>,
147
+ required: true,
148
+ },
149
+ totalItems: {
150
+ type: Number as PropType<number>,
151
+ required: true,
152
+ },
153
+ itemsPerPage: {
154
+ type: Number as PropType<number>,
155
+ default: 10,
156
+ },
157
+ showRowsPerPage: {
158
+ type: Boolean as PropType<boolean>,
159
+ default: true,
160
+ },
161
+ rowsPerPageLabel: String as PropType<string>,
162
+ rowsPerPageOptions: Array as PropType<PaginationRowPerPageOption[]>,
163
+ resultTextMultiplePages: {
164
+ type: String as PropType<string>,
165
+ default: 'Showing {from} to {to} of {total} results',
166
+ },
167
+ resultTextSinglePage: {
168
+ type: String as PropType<string>,
169
+ default: 'Showing {total} results',
170
+ },
171
+ resultTextSingleItem: {
172
+ type: String as PropType<string>,
173
+ default: 'Showing {total} result',
174
+ },
175
+ })
176
+
177
+ // Composables
178
+ const { isMobile } = useIsMobile()
179
+
180
+ // States
181
+ const itemsPerPage = ref(props.itemsPerPage)
182
+
183
+ // Emits
184
+ const emit = defineEmits(["update:modelValue", "update:itemsPerPage"])
185
+
186
+ // Computed
187
+ const totalPages = computed(() => Math.ceil(props.totalItems / itemsPerPage.value))
188
+
189
+ // Visible pages logic with ellipsis
190
+ const visiblePages = computed(() => {
191
+ const pages = []
192
+ const currentPage = props.modelValue
193
+ const total = totalPages.value
194
+
195
+ if (isMobile.value) {
196
+ const startPage = Math.max(1, currentPage - 1)
197
+ const endPage = Math.min(total, currentPage + 2)
198
+
199
+ for (let i = startPage; i <= endPage; i++) {
200
+ pages.push(i)
201
+ }
202
+
203
+ } else {
204
+ // Desktop: Include ellipsis with a maximum of 7 pages
205
+ const maxPagesDesktop = 7
206
+
207
+ if (total <= maxPagesDesktop) {
208
+ for (let i = 1; i <= total; i++) pages.push(i)
209
+ } else {
210
+ if (currentPage <= Math.floor(maxPagesDesktop / 2)) {
211
+ for (let i = 1; i <= maxPagesDesktop - 2; i++) pages.push(i)
212
+ pages.push('...', total)
213
+ } else if (currentPage >= total - Math.floor(maxPagesDesktop / 2)) {
214
+ pages.push(1, '...')
215
+ for (let i = total - (maxPagesDesktop - 3); i <= total; i++) pages.push(i)
216
+ } else {
217
+ pages.push(1, '...')
218
+ for (let i = currentPage - 1; i <= currentPage + 1; i++) pages.push(i)
219
+ pages.push('...', total)
220
+ }
221
+ }
222
+ }
223
+ return pages
224
+ })
225
+
226
+ const from = computed(() => (props.modelValue - 1) * itemsPerPage.value + 1)
227
+ const to = computed(() => Math.min(props.modelValue * itemsPerPage.value, props.totalItems))
228
+
229
+ const renderedResultsText = computed(() => {
230
+ // Multi-page case
231
+ if (props.totalItems > itemsPerPage.value) {
232
+ return props.resultTextMultiplePages
233
+ .replace('{from}', from.value.toString())
234
+ .replace('{to}', to.value.toString())
235
+ .replace('{total}', props.totalItems.toString())
236
+ .replace('{count}', props.totalItems === 1 ? 'result' : 'results')
237
+ }
238
+
239
+ // Single item case
240
+ if (props.totalItems === 1) {
241
+ return props.resultTextSingleItem
242
+ .replace('{total}', '1')
243
+ .replace('{count}', 'result')
244
+ }
245
+
246
+ // Single page with multiple results
247
+ return props.resultTextSinglePage
248
+ .replace('{total}', props.totalItems.toString())
249
+ })
250
+
251
+ // Handlers
252
+ const goToPreviousPage = () => {
253
+ if (props.modelValue > 1) {
254
+ emit("update:modelValue", props.modelValue - 1)
255
+ }
256
+ }
257
+
258
+ const goToNextPage = () => {
259
+ if (props.modelValue < totalPages.value) {
260
+ emit("update:modelValue", props.modelValue + 1)
261
+ }
262
+ }
263
+
264
+ const handlePageClick = (page: string | number) => {
265
+ if (page === '...') return
266
+ emit("update:modelValue", page)
267
+ }
268
+
269
+ // Watchers
270
+ watch(itemsPerPage, (newVal) => {
271
+ emit('update:modelValue', 1) // Reset to page 1 when rows per page changes
272
+ emit("update:itemsPerPage", newVal)
273
+ })
274
+ </script>
@@ -0,0 +1,60 @@
1
+ <template>
2
+ <div v-if="visible" class="flex items-center justify-center gap-3">
3
+ <span class="text-sm">
4
+ {{ rowsLabel }}
5
+ </span>
6
+ <DropdownSelect
7
+ :modelValue
8
+ :options="rowsOptions"
9
+ class="w-full min-w-[80px]"
10
+ :dropdownPosition="DropdownPosition.TOP"
11
+ @update:modelValue="(value) => emit('update:modelValue', value)"
12
+ />
13
+ </div>
14
+ </template>
15
+
16
+ <script setup lang="ts">
17
+ // Props
18
+ defineProps({
19
+ visible: {
20
+ type: Boolean as PropType<boolean>,
21
+ default: true,
22
+ },
23
+ modelValue: {
24
+ type: Number as PropType<number>,
25
+ default: 10,
26
+ },
27
+ rowsLabel: {
28
+ type: String as PropType<string>,
29
+ default: 'Rows',
30
+ },
31
+ rowsOptions: {
32
+ type: Array as PropType<PaginationRowPerPageOption[]>,
33
+ default: () => [
34
+ {
35
+ text: '5',
36
+ value: 5,
37
+ },
38
+ {
39
+ text: '10',
40
+ value: 10,
41
+ },
42
+ {
43
+ text: '25',
44
+ value: 25,
45
+ },
46
+ {
47
+ text: '50',
48
+ value: 50,
49
+ },
50
+ {
51
+ text: '100',
52
+ value: 100,
53
+ },
54
+ ]
55
+ }
56
+ })
57
+
58
+ // Emits
59
+ const emit = defineEmits(["update:modelValue"])
60
+ </script>
@@ -0,0 +1,97 @@
1
+ <template>
2
+ <nav class="flex items-center justify-center gap-2">
3
+ <ActionIconButton
4
+ icon="mdiChevronLeft"
5
+ :styleType="ButtonStyleType.NEUTRAL_TRANSPARENT"
6
+ :disabled="modelValue === 1"
7
+ :class="[
8
+ 'bg-transparent',
9
+ modelValue > 1 && 'hover:border border-border-default',
10
+ ]"
11
+ @click="goToPreviousPage"
12
+ />
13
+ <p class="text-sm">
14
+ {{ computedResultsText }}
15
+ </p>
16
+ <ActionIconButton
17
+ icon="mdiChevronRight"
18
+ :styleType="ButtonStyleType.NEUTRAL_TRANSPARENT"
19
+ :disabled="modelValue === totalPages"
20
+ :class="[
21
+ 'bg-transparent',
22
+ modelValue < totalPages && 'hover:border border-border-default',
23
+ ]"
24
+ @click="goToNextPage"
25
+ />
26
+ </nav>
27
+ </template>
28
+
29
+ <script setup lang="ts">
30
+
31
+ // Props
32
+ const props = defineProps({
33
+ modelValue: {
34
+ type: Number as PropType<number>,
35
+ required: true,
36
+ },
37
+ totalItems: {
38
+ type: Number as PropType<number>,
39
+ required: true,
40
+ },
41
+ itemsPerPage: {
42
+ type: Number as PropType<number>,
43
+ required: true,
44
+ },
45
+ resultTextMultiplePages: {
46
+ type: String as PropType<string>,
47
+ default: 'Showing {from} to {to} of {total} results',
48
+ },
49
+ resultTextSinglePage: {
50
+ type: String as PropType<string>,
51
+ default: 'Showing {total} results',
52
+ },
53
+ resultTextSingleItem: {
54
+ type: String as PropType<string>,
55
+ default: 'Showing {total} result',
56
+ },
57
+ })
58
+
59
+ // Emits
60
+ const emit = defineEmits(["update:modelValue"])
61
+
62
+ // Computed
63
+ const totalPages = computed(() => Math.ceil(props.totalItems / props.itemsPerPage))
64
+
65
+ const from = computed(() => (props.modelValue - 1) * props.itemsPerPage + 1)
66
+ const to = computed(() => Math.min(props.modelValue * props.itemsPerPage, props.totalItems))
67
+
68
+ const computedResultsText = computed(() => {
69
+ if (props.totalItems > props.itemsPerPage) {
70
+ return props.resultTextMultiplePages
71
+ .replace('{from}', from.value.toString())
72
+ .replace('{to}', to.value.toString())
73
+ .replace('{total}', props.totalItems.toString())
74
+ }
75
+
76
+ if (props.totalItems === 1) {
77
+ return props.resultTextSingleItem
78
+ .replace('{total}', '1')
79
+ }
80
+
81
+ return props.resultTextSinglePage
82
+ .replace('{total}', props.totalItems.toString())
83
+ })
84
+
85
+ // Handlers
86
+ const goToPreviousPage = () => {
87
+ if (props.modelValue > 1) {
88
+ emit("update:modelValue", props.modelValue - 1)
89
+ }
90
+ }
91
+
92
+ const goToNextPage = () => {
93
+ if (props.modelValue < totalPages.value) {
94
+ emit("update:modelValue", props.modelValue + 1)
95
+ }
96
+ }
97
+ </script>
@@ -0,0 +1,41 @@
1
+ <template>
2
+ <div
3
+ :class="[
4
+ 'flex',
5
+ 'flex-row',
6
+ 'items-center',
7
+ 'self-stretch',
8
+ 'gap-2',
9
+ ]"
10
+ >
11
+ <MdiIcon
12
+ :icon="isValid ? 'mdiCheck' : 'mdiClose'"
13
+ preserveAspectRatio="xMidYMid meet"
14
+ :class="[
15
+ isValid ? 'text-icon-success-subtle' : 'text-text-neutral-subtle',
16
+ 'w-[16px] h-[16px] min-w-[16px] min-h-[16px]',
17
+ ]"
18
+ />
19
+ <span
20
+ :class="[
21
+ 'text-sm',
22
+ isValid ? 'text-success-default' : 'text-text-neutral-subtle'
23
+ ]"
24
+ >
25
+ {{ label }}
26
+ </span>
27
+ </div>
28
+ </template>
29
+ <script setup lang="ts">
30
+ // Props
31
+ defineProps({
32
+ label: {
33
+ type: String as PropType<string>,
34
+ default: 'Condition label'
35
+ },
36
+ isValid: {
37
+ type: Boolean as PropType<boolean>,
38
+ required: true,
39
+ },
40
+ })
41
+ </script>
@@ -0,0 +1,83 @@
1
+ <template>
2
+ <div
3
+ :class="[
4
+ 'flex',
5
+ 'flex-col',
6
+ 'items-start',
7
+ 'gap-2',
8
+ ]"
9
+ >
10
+ <SecurePasswordCondition
11
+ v-if="enabledConditions.includes('length')"
12
+ :label="$t('Password should be at least 12 characters long.')"
13
+ :isValid="conditions.isLongEnough"
14
+ />
15
+
16
+ <SecurePasswordCondition
17
+ v-if="enabledConditions.includes('combination')"
18
+ :label="$t('Use a mix of uppercase and lowercase letters.')"
19
+ :isValid="conditions.hasMixedCase"
20
+ />
21
+
22
+ <SecurePasswordCondition
23
+ v-if="enabledConditions.includes('specialCharacters')"
24
+ :label="$t('Include numbers and special characters for extra security.')"
25
+ :isValid="conditions.hasNumbersAndSpecialChars"
26
+ />
27
+
28
+ <SecurePasswordCondition
29
+ v-if="enabledConditions.includes('passwordMatch') && repeatPassword !== undefined"
30
+ :label="$t('Both passwords must match.')"
31
+ :isValid="password === repeatPassword && password !== ''"
32
+ />
33
+ </div>
34
+ </template>
35
+
36
+ <script setup lang="ts">
37
+ // Props
38
+ const props = defineProps({
39
+ password: {
40
+ type: String as PropType<string>,
41
+ required: true,
42
+ },
43
+ repeatPassword: {
44
+ type: String as PropType<string>,
45
+ required: false,
46
+ },
47
+ enabledConditions: {
48
+ type: Array as PropType<Array<'length' | 'combination' | 'specialCharacters' | 'passwordMatch'>>,
49
+ default: () => ['length', 'combination', 'specialCharacters', 'passwordMatch'],
50
+ },
51
+ })
52
+
53
+ // Emits
54
+ const emit = defineEmits([
55
+ 'conditions-checked',
56
+ ])
57
+
58
+ // Computed states
59
+ const conditions = computed(() => evaluateSecurePasswordConditions(props.password))
60
+
61
+ // Watch & emit condition status
62
+ watchEffect(() => {
63
+ const checks: boolean[] = []
64
+
65
+ if (props.enabledConditions.includes('length')) {
66
+ checks.push(conditions.value.isLongEnough)
67
+ }
68
+
69
+ if (props.enabledConditions.includes('combination')) {
70
+ checks.push(conditions.value.hasMixedCase)
71
+ }
72
+
73
+ if (props.enabledConditions.includes('specialCharacters')) {
74
+ checks.push(conditions.value.hasNumbersAndSpecialChars)
75
+ }
76
+
77
+ if (props.enabledConditions.includes('passwordMatch') && props.repeatPassword !== undefined) {
78
+ checks.push(props.password === props.repeatPassword)
79
+ }
80
+
81
+ emit('conditions-checked', checks.every(Boolean))
82
+ })
83
+ </script>
@@ -0,0 +1,41 @@
1
+ <template>
2
+ <div
3
+ :class="[
4
+ 'w-full',
5
+ 'flex flex-col justify-center items-center',
6
+ 'p-6',
7
+ 'bg-background-primary-brand-soft',
8
+ 'rounded-sm',
9
+ 'bg-diagonal-stripes',
10
+ ]"
11
+ >
12
+ <div
13
+ :class="[
14
+ 'bg-[#f3f4f6]',
15
+ 'px-2',
16
+ 'py-1',
17
+ 'rounded',
18
+ textWrapperClass,
19
+ ]">
20
+ <span
21
+ :class="[
22
+ 'text-text-neutral-subtle-on-subtler-bg',
23
+ textClass,
24
+ ]">
25
+ {{ text }}
26
+ </span>
27
+ </div>
28
+ </div>
29
+ </template>
30
+
31
+ <script setup lang="ts">
32
+ // Props
33
+ defineProps({
34
+ text: {
35
+ type: String as PropType<string>,
36
+ default: 'Replace me',
37
+ },
38
+ textWrapperClass: String as PropType<string>,
39
+ textClass: String as PropType<string>,
40
+ })
41
+ </script>