@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,124 @@
1
+ <template>
2
+ <div
3
+ :class="[
4
+ 'relative flex items-center justify-center',
5
+ hasBorder && 'border border-border-default rounded-md p-2',
6
+ containerClass,
7
+ ]"
8
+ :style="{
9
+ width: `${size}px`,
10
+ height: `${size}px`,
11
+ }"
12
+ >
13
+ <Spinner v-if="isLoading" />
14
+
15
+ <QrcodeVue
16
+ v-else
17
+ :value="modelValue"
18
+ :level
19
+ :render-as="renderAs"
20
+ :background
21
+ :foreground
22
+ :gradient="useGradient"
23
+ :gradient-type="gradientType"
24
+ :gradient-start-color="gradientStartColor"
25
+ :gradient-end-color="gradientEndColor"
26
+ :image-settings="computedImageSettings"
27
+ :margin="imageMargin"
28
+ :size="qrSize"
29
+ />
30
+ </div>
31
+ </template>
32
+
33
+ <script setup lang="ts">
34
+ // Imports
35
+ import QrcodeVue from 'qrcode.vue'
36
+ import type { Level, RenderAs, GradientType, ImageSettings } from 'qrcode.vue'
37
+
38
+ // Props
39
+ const props = defineProps({
40
+ renderAs: {
41
+ type: String as PropType<QRRenderAs>,
42
+ default: QRRenderAs.CANVAS,
43
+ validator: (as: QRRenderAs) => Object.values(QRRenderAs).includes(as),
44
+ },
45
+ modelValue: {
46
+ type: String as PropType<string>,
47
+ required: true,
48
+ },
49
+ size: {
50
+ type: Number as PropType<number>,
51
+ default: 250,
52
+ },
53
+ level: {
54
+ type: String as PropType<QRLevel>,
55
+ default: QRLevel.M,
56
+ validator: (level: QRLevel) => Object.values(QRLevel).includes(level),
57
+ },
58
+ background: {
59
+ type: String as PropType<string>,
60
+ default: '#ffffff',
61
+ },
62
+ foreground: {
63
+ type: String as PropType<string>,
64
+ default: '#000000',
65
+ },
66
+ useGradient: {
67
+ type: Boolean as PropType<boolean>,
68
+ default: false,
69
+ },
70
+ gradientType: {
71
+ type: String as PropType<QRGradientType>,
72
+ default: QRGradientType.LINEAR,
73
+ validator: (type: QRGradientType) => Object.values(QRGradientType).includes(type),
74
+ },
75
+ gradientStartColor: {
76
+ type: String as PropType<string>,
77
+ default: '#ffffff',
78
+ },
79
+ gradientEndColor: {
80
+ type: String as PropType<string>,
81
+ default: '#000000',
82
+ },
83
+ imageMargin: {
84
+ type: Number as PropType<number>,
85
+ default: 0,
86
+ },
87
+ hasBorder: {
88
+ type: Boolean as PropType<boolean>,
89
+ default: true,
90
+ },
91
+ hasMiddleImage: {
92
+ type: Boolean as PropType<boolean>,
93
+ default: false,
94
+ },
95
+ imageSettings: Object as PropType<ImageSettings>,
96
+ isLoading: {
97
+ type: Boolean as PropType<boolean>,
98
+ default: false,
99
+ },
100
+ containerClass: String as PropType<string>,
101
+ })
102
+
103
+ // Computed
104
+ const computedImageSettings = computed(() => {
105
+ if (!props.hasMiddleImage) return undefined
106
+
107
+ if (props.imageSettings) {
108
+ return props.imageSettings
109
+ } else {
110
+ return {
111
+ src: 'https://github.com/scopewu.png',
112
+ width: 30,
113
+ height: 30,
114
+ excavate: true,
115
+ }
116
+ }
117
+ })
118
+
119
+ const containerPaddingPx = 4
120
+
121
+ const qrSize = computed(() => {
122
+ return props.size - props.imageMargin * 2 - containerPaddingPx
123
+ })
124
+ </script>
@@ -0,0 +1,13 @@
1
+ <template>
2
+ <div
3
+ :class="[
4
+ 'flex',
5
+ 'flex-col',
6
+ 'gap-6',
7
+ 'bg-white',
8
+ 'py-4 md:py-8'
9
+ ]"
10
+ >
11
+ <slot></slot>
12
+ </div>
13
+ </template>
@@ -0,0 +1,30 @@
1
+ <template>
2
+ <div
3
+ :class="[
4
+ 'flex flex-col pb-8 transition-all duration-300',
5
+ hasSidebar && (
6
+ isMobile
7
+ ? isMobileSidebarOpen
8
+ ? 'translate-x-[240px]'
9
+ : 'translate-x-0'
10
+ : 'ml-[240px]'
11
+ ),
12
+ ]"
13
+ >
14
+ <slot />
15
+ </div>
16
+ </template>
17
+
18
+ <script setup lang="ts">
19
+ // Props
20
+ defineProps({
21
+ hasSidebar: {
22
+ type: Boolean as PropType<boolean>,
23
+ default: false
24
+ }
25
+ })
26
+
27
+ // Composables
28
+ const { isMobileSidebarOpen } = useMobileSidebar()
29
+ const { isMobile } = useIsMobile()
30
+ </script>
@@ -0,0 +1,25 @@
1
+ <template>
2
+ <div :class="gridClasses">
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ const props = defineProps({
9
+ cols: { // Desktop
10
+ type: Number as PropType<number>,
11
+ default: 3,
12
+ },
13
+ tabletCols: {
14
+ type: Number as PropType<number>,
15
+ default: 2,
16
+ },
17
+ mobileCols: {
18
+ type: Number as PropType<number>,
19
+ default: 1,
20
+ },
21
+ })
22
+
23
+ // Computed classes
24
+ const gridClasses = computed(() => getGridClasses(props.cols, props.tabletCols, props.mobileCols))
25
+ </script>
@@ -0,0 +1,159 @@
1
+ <template>
2
+ <div
3
+ :class="[
4
+ 'w-full flex flex-col',
5
+ alignmentClasses,
6
+ titleClass,
7
+ ]"
8
+ >
9
+ <!-- Overtitle -->
10
+ <p
11
+ v-if="overtitle"
12
+ :class="[
13
+ 'font-semibold',
14
+ overtitleSizeClass,
15
+ 'text-text-secondary-brand-default',
16
+ spaceOvertitleClass,
17
+ ]"
18
+ >
19
+ {{ overtitle }}
20
+ </p>
21
+
22
+ <!-- Dynamic title -->
23
+ <component
24
+ :is="headingTag"
25
+ :class="[titleSizeClass, 'font-semibold', 'text-text-default']"
26
+ >
27
+ {{ title }}
28
+ </component>
29
+
30
+ <!-- Description -->
31
+ <p
32
+ v-if="description"
33
+ :class="[descriptionSizeClass, 'text-text-neutral-subtle', spaceDescriptionClass]"
34
+ >
35
+ {{ description }}
36
+ </p>
37
+ </div>
38
+ </template>
39
+
40
+ <script setup lang="ts">
41
+ // Props
42
+ const props = defineProps({
43
+ overtitle: String as PropType<string>,
44
+ title: {
45
+ type: String as PropType<string>,
46
+ default: 'Heading title'
47
+ },
48
+ description: String as PropType<string>,
49
+ align: {
50
+ type: String as PropType<Align>,
51
+ default: Align.LEFT,
52
+ validator: (value: string) => Object.values(Align).includes(value as Align),
53
+ },
54
+ size: {
55
+ type: String as PropType<HeadingSize>,
56
+ default: HeadingSize.LG,
57
+ validator: (value: string) =>
58
+ Object.values(HeadingSize).includes(value as HeadingSize),
59
+ },
60
+ spacing: {
61
+ type: String as PropType<HeadingSpacing>,
62
+ default: HeadingSpacing.NORMAL,
63
+ validator: (value: string) =>
64
+ Object.values(HeadingSpacing).includes(value as HeadingSpacing),
65
+ },
66
+ headingTag: {
67
+ type: [String, Number] as PropType<'h1' | 'h2' | 'h3'>,
68
+ default: 'h1',
69
+ validator: (value: string | number) => ['h1', 'h2', 'h3'].includes(value as string)
70
+ },
71
+ isMobileCentered: {
72
+ type: Boolean as PropType<boolean>,
73
+ default: false,
74
+ },
75
+ titleClass: String as PropType<string>,
76
+ })
77
+
78
+ // Computed classes
79
+ const alignmentClasses = computed(() => {
80
+ const alignMap = {
81
+ [Align.LEFT]: 'lg:items-start lg:text-left',
82
+ [Align.CENTER]: 'lg:items-center lg:text-center',
83
+ [Align.RIGHT]: 'lg:items-end lg:text-right',
84
+ }
85
+
86
+ if (props.isMobileCentered) {
87
+ // Centered on mobile, and follow desktop alignment rules
88
+ return ['items-center text-center', alignMap[props.align]].join(' ')
89
+ }
90
+
91
+ // Normal mobile alignment follows main alignment
92
+ const baseAlignMap = {
93
+ [Align.LEFT]: 'items-start text-left',
94
+ [Align.CENTER]: 'items-center text-center',
95
+ [Align.RIGHT]: 'items-end text-right',
96
+ }
97
+
98
+ return [baseAlignMap[props.align], alignMap[props.align]].join(' ')
99
+ })
100
+
101
+ const titleSizeClass = computed(() => {
102
+ const map = {
103
+ [HeadingSize.SM]: 'text-2xl',
104
+ [HeadingSize.MD]: 'text-3xl md:text-4xl',
105
+ [HeadingSize.LG]: 'text-4xl md:text-5xl',
106
+ [HeadingSize.XL]: 'text-5xl md:text-7xl',
107
+ }
108
+ return map[props.size] || 'text-4xl md:text-5xl'
109
+ })
110
+
111
+ const overtitleSizeClass = computed(() => {
112
+ const map = {
113
+ [HeadingSize.SM]: 'text-sm',
114
+ [HeadingSize.MD]: 'text-base',
115
+ [HeadingSize.LG]: 'text-base',
116
+ [HeadingSize.XL]: 'text-base',
117
+ }
118
+ return map[props.size] || 'text-base'
119
+ })
120
+
121
+ const descriptionSizeClass = computed(() => {
122
+ const map = {
123
+ [HeadingSize.SM]: 'text-sm',
124
+ [HeadingSize.MD]: 'text-lg',
125
+ [HeadingSize.LG]: 'text-lg',
126
+ [HeadingSize.XL]: 'text-lg',
127
+ }
128
+ return map[props.size] || 'text-lg'
129
+ })
130
+
131
+ const spaceOvertitleClass = computed(() => {
132
+ const map = {
133
+ [HeadingSize.SM]: 'mb-1',
134
+ [HeadingSize.MD]: 'mb-3',
135
+ [HeadingSize.LG]: 'mb-3',
136
+ [HeadingSize.XL]: 'mb-3',
137
+ }
138
+ return map[props.size] || 'mb-3'
139
+ })
140
+
141
+ const spaceDescriptionClass = computed(() => {
142
+ const normal = {
143
+ [HeadingSize.SM]: 'mt-3',
144
+ [HeadingSize.MD]: 'mt-4',
145
+ [HeadingSize.LG]: 'mt-6',
146
+ [HeadingSize.XL]: 'mt-4',
147
+ }
148
+ const spaced = {
149
+ [HeadingSize.SM]: 'mt-4',
150
+ [HeadingSize.MD]: 'mt-5',
151
+ [HeadingSize.LG]: 'mt-9',
152
+ [HeadingSize.XL]: 'mt-5',
153
+ }
154
+
155
+ return props.spacing === HeadingSpacing.SPACED
156
+ ? spaced[props.size]
157
+ : normal[props.size] || 'mt-6'
158
+ })
159
+ </script>
@@ -0,0 +1,26 @@
1
+ <template>
2
+ <main
3
+ :class="[
4
+ 'flex',
5
+ 'flex-col',
6
+ 'gap-3',
7
+ 'w-full',
8
+ ]"
9
+ :style="!isMobile && `max-width: calc(100% - ${tocSidebarWidth}px)`"
10
+ >
11
+ <slot />
12
+ </main>
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ // Props
17
+ defineProps({
18
+ tocSidebarWidth: {
19
+ type: Number as PropType<number>,
20
+ default: 240
21
+ }
22
+ })
23
+
24
+ // Composable
25
+ const { isMobile } = useIsMobile()
26
+ </script>
@@ -0,0 +1,15 @@
1
+ <template>
2
+ <div
3
+ :class="[
4
+ 'flex',
5
+ 'flex-col',
6
+ 'gap-3',
7
+ 'items-center',
8
+ 'justify-center',
9
+ 'w-full',
10
+ 'max-w-[1440px]',
11
+ ]"
12
+ >
13
+ <slot />
14
+ </div>
15
+ </template>
@@ -0,0 +1,25 @@
1
+ <template>
2
+ <span
3
+ :class="[
4
+ 'text-text-primary-brand-default',
5
+ 'text-sm',
6
+ 'font-medium',
7
+ isUppercase && 'uppercase',
8
+ ]"
9
+ >
10
+ {{ title }}
11
+ </span>
12
+ </template>
13
+ <script setup lang="ts">
14
+ // Props
15
+ defineProps({
16
+ title: {
17
+ type: String as PropType<string>,
18
+ default: 'Overtitle'
19
+ },
20
+ isUppercase: {
21
+ type: Boolean as PropType<boolean>,
22
+ default: true,
23
+ }
24
+ })
25
+ </script>
@@ -0,0 +1,181 @@
1
+ <template>
2
+ <header
3
+ :class="[
4
+ isSticky && 'sticky top-0 z-50'
5
+ ]"
6
+ >
7
+ <slot name="top-header" />
8
+ <!-- Main header wrapper -->
9
+ <div
10
+ :class="[
11
+ 'flex',
12
+ 'items-center',
13
+ 'justify-between',
14
+ 'gap-3',
15
+ 'px-content-side-padding-mobile md:px-content-side-padding',
16
+ 'py-4',
17
+ 'bg-background-surface',
18
+ hasBorder && 'border-b border-border-default',
19
+ isSticky && 'sticky top-0 z-50',
20
+ hasGlassEffect && [
21
+ 'backdrop-blur-md',
22
+ 'bg-background-surface/30 dark:bg-background-surface/85',
23
+ ],
24
+ containerClass,
25
+ ]"
26
+ >
27
+ <!-- Logo -->
28
+ <div class="flex gap-4">
29
+ <slot name="header-logo" />
30
+ </div>
31
+
32
+ <!-- Navigation -->
33
+ <div class="flex gap-3 items-center xs:w-auto">
34
+ <!-- Horizontal menu -->
35
+ <NavMenu
36
+ v-if="navMenuItems.length"
37
+ :menuItems="navMenuItems"
38
+ :detectActive="detectActiveMenuItem"
39
+ :class="navMenuClass"
40
+ />
41
+
42
+ <!-- Header actions -->
43
+ <div
44
+ v-if="$slots['header-actions']"
45
+ class="gap-3 items-center hidden lg:flex"
46
+ >
47
+ <slot name="header-actions" />
48
+ </div>
49
+
50
+ <!-- User Menu -->
51
+ <DropdownMenu
52
+ v-if="userMenuItems.length && userFullname"
53
+ class="min-w-[200px]"
54
+ >
55
+ <template #activator="{ onClick }">
56
+ <Avatar
57
+ :size="AvatarSize.MD"
58
+ isInteractive
59
+ :displayName="userFullname"
60
+ :imgUrl="userAvatarUrl"
61
+ @click="onClick"
62
+ />
63
+ </template>
64
+ <template #items>
65
+ <DropdownMenuItem
66
+ v-for="item in userMenuItems" :key="item.text"
67
+ :text="item.text"
68
+ :icon="item.icon"
69
+ :type="item.type"
70
+ :imgUrl="item.imgUrl"
71
+ :alt="item.imgUrl"
72
+ :to="item.to"
73
+ :isExternal="item.isExternal"
74
+ :actionType="item.actionType"
75
+ @click="item.callback"
76
+ />
77
+ </template>
78
+ </DropdownMenu>
79
+
80
+ <!-- Mobile menu -->
81
+ <template v-if="isMobile && mobileMenuType === MobileNavigationMenuType.DROPDOWN">
82
+ <DropdownMenu :class="navMobileMenuClass">
83
+ <template #activator="{ onClick }">
84
+ <ActionIconButton
85
+ icon="mdiMenu"
86
+ class="lg:hidden shadow-sm"
87
+ @click="onClick"
88
+ />
89
+ </template>
90
+ <template #items>
91
+ <DropdownMenuItem
92
+ v-for="item in navMenuItems" :key="item.text"
93
+ :text="item.text"
94
+ :to="item.to"
95
+ />
96
+
97
+ <DropdownMenuActions v-if="$slots['header-actions']">
98
+ <slot name="header-actions" />
99
+ </DropdownMenuActions>
100
+ </template>
101
+ </DropdownMenu>
102
+ </template>
103
+
104
+ <template v-else-if="mobileMenuType === MobileNavigationMenuType.SIDEBAR">
105
+ <ActionIconButton
106
+ :icon="isMobileSidebarOpen ? 'mdiMenuOpen' : 'mdiMenuClose'"
107
+ class="lg:hidden shadow-sm"
108
+ @click="toggleMobileSidebar"
109
+ />
110
+ </template>
111
+
112
+ </div>
113
+ </div>
114
+ <slot name="bottom-header" />
115
+ </header>
116
+ </template>
117
+
118
+ <script setup lang="ts">
119
+ // Props
120
+ defineProps({
121
+ pageTitleType: {
122
+ type: String as PropType<PageTitleType>,
123
+ default: PageTitleType.SIMPLE,
124
+ },
125
+ navMenuItems: {
126
+ type: Array as PropType<MenuItem[]>,
127
+ default: () => [],
128
+ },
129
+ userFullname: String as PropType<string>,
130
+ userAvatarUrl: String as PropType<string>,
131
+ userMenuItems: {
132
+ type: Array as PropType<DropdownMenuItem[]>,
133
+ default: () => [],
134
+ },
135
+ mobileMenuType: {
136
+ type: String as PropType<MobileNavigationMenuType>,
137
+ default: MobileNavigationMenuType.DROPDOWN,
138
+ validator: (value: MobileNavigationMenuType) => Object.values(MobileNavigationMenuType).includes(value),
139
+ },
140
+ hasBorder: {
141
+ type: Boolean as PropType<boolean>,
142
+ default: false,
143
+ },
144
+ isSticky: {
145
+ type: Boolean as PropType<boolean>,
146
+ default: false,
147
+ },
148
+ hasGlassEffect: {
149
+ type: Boolean as PropType<boolean>,
150
+ default: false,
151
+ },
152
+ detectActiveMenuItem: {
153
+ type: Boolean,
154
+ default: true,
155
+ },
156
+ navMenuClass: {
157
+ type: String as PropType<string>,
158
+ default: 'hidden lg:flex'
159
+ },
160
+ navMobileMenuClass: {
161
+ type: String as PropType<string>,
162
+ default: 'lg:hidden min-w-[280px]'
163
+ },
164
+ containerClass: String as PropType<string>,
165
+ })
166
+
167
+ // Composables
168
+ const { isMobileSidebarOpen, toggleMobileSidebar } = useMobileSidebar()
169
+ const { isMobile } = useIsMobile()
170
+
171
+ // Page title
172
+ const route = useRoute()
173
+ const currentPageTitle = computed<string>(() =>
174
+ (route.meta.title as string) ?? 'Page title'
175
+ )
176
+
177
+ // Dynamically set the page title
178
+ useHead(() => ({
179
+ title: pageTitle(currentPageTitle.value, App.NAME),
180
+ }))
181
+ </script>