@imaginario27/air-ui-ds 1.0.16 → 1.0.18

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.
@@ -0,0 +1,238 @@
1
+ <template>
2
+ <div
3
+ :class="[
4
+ 'w-full',
5
+ isFullScreen && 'h-screen',
6
+ 'px-content-side-padding',
7
+ 'py-[10vw] md:py-[20vw]',
8
+ 'flex',
9
+ orientation === Orientation.VERTICAL ? 'flex-col' : 'flex-col md:flex-row',
10
+ 'gap-9',
11
+ 'items-center',
12
+ 'justify-center',
13
+ ]"
14
+ >
15
+ <!-- Slot for images, illustrations or other visual elements -->
16
+ <slot name="visual-left" />
17
+
18
+ <!-- Body -->
19
+ <div
20
+ :class="[
21
+ 'w-full',
22
+ 'gap-9',
23
+ 'flex',
24
+ 'flex-col',
25
+ contentAlignmentClass,
26
+ ]"
27
+ >
28
+ <slot name="visual-top" />
29
+
30
+ <ContainedIcon
31
+ v-if="!$slots['visual-top'] && showIcon"
32
+ :color="ColorAccent.DANGER"
33
+ :icon
34
+ :size="IconContainerSize.XXL"
35
+ />
36
+
37
+ <div
38
+ v-if="!$slots['description']"
39
+ :class="[
40
+ 'flex',
41
+ 'flex-col',
42
+ 'gap-4',
43
+ contentAlignmentClass,
44
+ 'w-full',
45
+ 'max-w-[600px]',
46
+ ]"
47
+ >
48
+ <!--
49
+ Overtitle will only display if useGenericErrorTitle is true.
50
+ Otherwise, it will not be displayed because the error code is shown as the title
51
+ -->
52
+ <Heading
53
+ :title="errorTitle"
54
+ :overtitle="useGenericErrorTitle ? error.statusCode.toString() : ''"
55
+ :align="alignContent"
56
+ :size="HeadingSize.MD"
57
+ :isMobileCentered
58
+ />
59
+
60
+ <p
61
+ :class="[
62
+ 'text-text-neutral-subtle',
63
+ 'font-semibold',
64
+ textAlignmentClass,
65
+ 'leading-6',
66
+ ]"
67
+ >
68
+ {{
69
+ error.statusCode === 404
70
+ ? pageNotFoundMessage
71
+ : error.message ?? genericErrorMessage
72
+ }}
73
+ </p>
74
+ </div>
75
+
76
+ <slot name="description" />
77
+
78
+ <!-- Actions -->
79
+ <div
80
+ :class="[
81
+ 'w-full',
82
+ 'flex',
83
+ 'gap-3',
84
+ actionsAlignmentClass,
85
+ 'flex-col',
86
+ 'md:flex-row',
87
+ 'mb-4 md:mb-10' // Visual fix to push content a bit up
88
+ ]"
89
+ >
90
+ <ActionButton
91
+ v-if="!$slots['actions']"
92
+ :actionType="ButtonActionType.LINK"
93
+ :text="backToHomeText"
94
+ class="w-full md:w-auto"
95
+ :icon="backToHomeIcon"
96
+ :iconPosition="IconPosition.LEFT"
97
+ :to="homeRoute"
98
+ />
99
+
100
+ <slot name="actions" />
101
+ </div>
102
+ </div>
103
+
104
+ <!-- Slot for images, illustrations or other visual elements -->
105
+ <slot name="visual-right" />
106
+ </div>
107
+ </template>
108
+ <script setup lang="ts">
109
+ // Imports
110
+ import type { NuxtError } from "#app"
111
+
112
+ // Props
113
+ const props = defineProps({
114
+ error: {
115
+ type: Object as PropType<NuxtError>,
116
+ required: true,
117
+ },
118
+ showIcon: {
119
+ type: Boolean as PropType<boolean>,
120
+ default: true,
121
+ },
122
+ icon: {
123
+ type: String as PropType<any>,
124
+ default: "mdiAlertCircleOutline",
125
+ },
126
+ pageNotFoundTitle: {
127
+ type: String as PropType<string>,
128
+ default: "Page not found",
129
+ },
130
+ pageNotFoundMessage: {
131
+ type: String as PropType<string>,
132
+ default: "The page you are looking for does not exist or may have been moved. Please try with another page.",
133
+ },
134
+ useGenericErrorTitle: {
135
+ type: Boolean as PropType<boolean>,
136
+ default: false,
137
+ },
138
+ genericErrorTitle: {
139
+ type: String as PropType<string>,
140
+ default: "Oops! Something went wrong",
141
+ },
142
+ genericErrorMessage: {
143
+ type: String as PropType<string>,
144
+ default: "Something went wrong on our end. Please try again later.",
145
+ },
146
+ backToHomeText: {
147
+ type: String as PropType<string>,
148
+ default: "Back to home page",
149
+ },
150
+ backToHomeIcon: {
151
+ type: String as PropType<string>,
152
+ default: "mdiHomeOutline",
153
+ },
154
+ homeRoute: {
155
+ type: String as PropType<string>,
156
+ default: "/",
157
+ },
158
+ isFullScreen: {
159
+ type: Boolean as PropType<boolean>,
160
+ default: true,
161
+ },
162
+ orientation: {
163
+ type: String as PropType<Orientation>,
164
+ default: Orientation.HORIZONTAL,
165
+ validator: (value: Orientation) => Object.values(Orientation).includes(value),
166
+ },
167
+ alignContent: {
168
+ type: String as PropType<Align>,
169
+ default: Align.LEFT,
170
+ validator: (value: Align) => Object.values(Align).includes(value),
171
+ },
172
+ isMobileCentered: {
173
+ type: Boolean as PropType<boolean>,
174
+ default: true,
175
+ },
176
+ })
177
+
178
+ // Computed
179
+ const errorTitle = computed(() => {
180
+ if (props.useGenericErrorTitle) {
181
+ return props.genericErrorTitle
182
+ }
183
+
184
+ if (props.error.statusCode === 404 || props.error.statusCode === 400) {
185
+ return props.pageNotFoundTitle
186
+ }
187
+
188
+ return String(props.error.statusCode)
189
+ })
190
+
191
+ const pageTitleText = computed(() => errorTitle.value)
192
+
193
+ // Dynamically set the page title with a watcher (only for error page)
194
+ watchEffect(() => {
195
+ document.title = pageTitle(pageTitleText.value, App.NAME)
196
+ })
197
+
198
+ // Computed classes
199
+ const contentAlignmentClass = computed(() => {
200
+ const base = {
201
+ [Align.LEFT]: 'md:items-start',
202
+ [Align.CENTER]: 'md:items-center',
203
+ [Align.RIGHT]: 'md:items-end',
204
+ }
205
+
206
+ return [
207
+ props.isMobileCentered ? 'items-center' : '',
208
+ base[props.alignContent as Align] || 'md:items-center',
209
+ ].join(' ').trim()
210
+ })
211
+
212
+ const actionsAlignmentClass = computed(() => {
213
+ const base = {
214
+ [Align.LEFT]: 'md:items-start md:justify-start',
215
+ [Align.CENTER]: 'md:items-center md:justify-center',
216
+ [Align.RIGHT]: 'md:items-end md:justify-end',
217
+ }
218
+
219
+ return [
220
+ props.isMobileCentered ? 'items-center justify-center' : '',
221
+ base[props.alignContent as Align] || 'md:items-center md:justify-center',
222
+ ].join(' ').trim()
223
+ })
224
+
225
+ const textAlignmentClass = computed(() => {
226
+ const base = {
227
+ [Align.LEFT]: 'md:text-left',
228
+ [Align.CENTER]: 'md:text-center',
229
+ [Align.RIGHT]: 'md:text-right',
230
+ }
231
+
232
+ return [
233
+ props.isMobileCentered ? 'text-center' : '',
234
+ base[props.alignContent as Align] || 'md:text-center',
235
+ ].join(' ').trim()
236
+ })
237
+
238
+ </script>
@@ -0,0 +1,110 @@
1
+ <template>
2
+ <footer
3
+ :class="[
4
+ 'w-full',
5
+ 'bg-background-surface',
6
+ 'py-section-xs',
7
+ hasSidePadding && 'px-content-side-padding-mobile md:px-content-side-padding',
8
+ ]"
9
+ >
10
+ <MaxWidthContainer
11
+ :class="[!hasContentMaxWidth && '!max-w-full']"
12
+ >
13
+ <template v-if="!$slots['default']">
14
+ <div
15
+ :class="[
16
+ 'w-full',
17
+ 'flex',
18
+ 'flex-col',
19
+ isMobileCentered &&'items-center text-center',
20
+ 'gap-10',
21
+ 'lg:flex-row',
22
+ 'lg:items-start',
23
+ 'lg:text-left',
24
+ 'lg:justify-between',
25
+ ]"
26
+ >
27
+ <span class="text-sm text-text-neutral-subtle">
28
+ {{ credits }}
29
+ </span>
30
+
31
+ <nav v-if="menuItems.length">
32
+ <ul class="flex flex-col lg:flex-row gap-5">
33
+ <li
34
+ v-for="(item, index) in menuItems"
35
+ :key="index"
36
+ >
37
+ <NuxtLink
38
+ :to="item.to"
39
+ :class="[
40
+ 'text-sm',
41
+ 'hover:text-text-primary-brand-hover',
42
+ 'transition-colors',
43
+ ]"
44
+ >
45
+ {{ item.text }}
46
+ </NuxtLink>
47
+ </li>
48
+ </ul>
49
+ </nav>
50
+
51
+ <div v-if="socialNetworks.length" class="flex gap-5">
52
+ <a
53
+ v-for="(network, index) in socialNetworks"
54
+ :key="index"
55
+ :href="network.link"
56
+ target="_blank"
57
+ rel="noopener noreferrer"
58
+ :class="[
59
+ 'text-text-neutral-subtle',
60
+ 'hover:text-text-primary-brand-hover',
61
+ 'transition-colors',
62
+ ]"
63
+ :aria-label="network.name"
64
+ >
65
+ <img
66
+ :src="network.icon"
67
+ :alt="`${network.name} icon`"
68
+ width="20"
69
+ height="20"
70
+ class="min-w-[20px] min-h-[20px]"
71
+ />
72
+ </a>
73
+ </div>
74
+ </div>
75
+ </template>
76
+ <template v-else>
77
+ <slot />
78
+ </template>
79
+ </MaxWidthContainer>
80
+ </footer>
81
+ </template>
82
+ <script setup lang="ts">
83
+ // Props
84
+ defineProps({
85
+ credits: {
86
+ type: String as PropType<string>,
87
+ default: '© <year> <your-company>. All rights reserved.',
88
+ },
89
+ menuItems: {
90
+ type: Array as PropType<MenuItem[]>,
91
+ default: () => [],
92
+ },
93
+ socialNetworks: {
94
+ type: Array as PropType<SocialNetwork[]>,
95
+ default: () => [],
96
+ },
97
+ hasContentMaxWidth: {
98
+ type: Boolean as PropType<boolean>,
99
+ default: false,
100
+ },
101
+ hasSidePadding: {
102
+ type: Boolean as PropType<boolean>,
103
+ default: true,
104
+ },
105
+ isMobileCentered: {
106
+ type: Boolean as PropType<boolean>,
107
+ default: false,
108
+ },
109
+ })
110
+ </script>
@@ -3,26 +3,32 @@
3
3
  :class="[
4
4
  'w-full flex flex-col',
5
5
  alignmentClasses,
6
- titleClass,
7
6
  ]"
8
7
  >
9
8
  <!-- Overtitle -->
10
- <p
9
+ <span
11
10
  v-if="overtitle"
12
11
  :class="[
13
12
  'font-semibold',
14
13
  overtitleSizeClass,
15
14
  'text-text-secondary-brand-default',
16
15
  spaceOvertitleClass,
16
+ isOverTitleUppercase && 'uppercase',
17
+ overtitleClass,
17
18
  ]"
18
19
  >
19
20
  {{ overtitle }}
20
- </p>
21
+ </span>
21
22
 
22
23
  <!-- Dynamic title -->
23
24
  <component
24
25
  :is="headingTag"
25
- :class="[titleSizeClass, 'font-semibold', 'text-text-default']"
26
+ :class="[
27
+ titleSizeClass,
28
+ 'font-semibold',
29
+ 'text-text-default',
30
+ titleClass,
31
+ ]"
26
32
  >
27
33
  {{ title }}
28
34
  </component>
@@ -30,7 +36,12 @@
30
36
  <!-- Description -->
31
37
  <p
32
38
  v-if="description"
33
- :class="[descriptionSizeClass, 'text-text-neutral-subtle', spaceDescriptionClass]"
39
+ :class="[
40
+ descriptionSizeClass,
41
+ 'text-text-neutral-subtle',
42
+ spaceDescriptionClass,
43
+ descriptionClass,
44
+ ]"
34
45
  >
35
46
  {{ description }}
36
47
  </p>
@@ -41,6 +52,10 @@
41
52
  // Props
42
53
  const props = defineProps({
43
54
  overtitle: String as PropType<string>,
55
+ isOverTitleUppercase: {
56
+ type: Boolean as PropType<boolean>,
57
+ default: false,
58
+ },
44
59
  title: {
45
60
  type: String as PropType<string>,
46
61
  default: 'Heading title'
@@ -64,23 +79,25 @@ const props = defineProps({
64
79
  Object.values(HeadingSpacing).includes(value as HeadingSpacing),
65
80
  },
66
81
  headingTag: {
67
- type: [String, Number] as PropType<'h1' | 'h2' | 'h3'>,
82
+ type: [String, Number] as PropType<'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'>,
68
83
  default: 'h1',
69
- validator: (value: string | number) => ['h1', 'h2', 'h3'].includes(value as string)
84
+ validator: (value: string | number) => ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(value as string)
70
85
  },
71
86
  isMobileCentered: {
72
87
  type: Boolean as PropType<boolean>,
73
88
  default: false,
74
89
  },
90
+ overtitleClass: String as PropType<string>,
75
91
  titleClass: String as PropType<string>,
92
+ descriptionClass: String as PropType<string>,
76
93
  })
77
94
 
78
95
  // Computed classes
79
96
  const alignmentClasses = computed(() => {
80
97
  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',
98
+ [Align.LEFT]: 'md:items-start md:text-left',
99
+ [Align.CENTER]: 'md:items-center md:text-center',
100
+ [Align.RIGHT]: 'md:items-end md:text-right',
84
101
  }
85
102
 
86
103
  if (props.isMobileCentered) {
@@ -100,6 +117,8 @@ const alignmentClasses = computed(() => {
100
117
 
101
118
  const titleSizeClass = computed(() => {
102
119
  const map = {
120
+ [HeadingSize.XXS]: 'text-lg',
121
+ [HeadingSize.XS]: 'text-xl',
103
122
  [HeadingSize.SM]: 'text-2xl',
104
123
  [HeadingSize.MD]: 'text-3xl md:text-4xl',
105
124
  [HeadingSize.LG]: 'text-4xl md:text-5xl',
@@ -110,6 +129,8 @@ const titleSizeClass = computed(() => {
110
129
 
111
130
  const overtitleSizeClass = computed(() => {
112
131
  const map = {
132
+ [HeadingSize.XXS]: 'text-xs',
133
+ [HeadingSize.XS]: 'text-sm',
113
134
  [HeadingSize.SM]: 'text-sm',
114
135
  [HeadingSize.MD]: 'text-base',
115
136
  [HeadingSize.LG]: 'text-base',
@@ -120,6 +141,8 @@ const overtitleSizeClass = computed(() => {
120
141
 
121
142
  const descriptionSizeClass = computed(() => {
122
143
  const map = {
144
+ [HeadingSize.XXS]: 'text-sm',
145
+ [HeadingSize.XS]: 'text-sm',
123
146
  [HeadingSize.SM]: 'text-sm',
124
147
  [HeadingSize.MD]: 'text-lg',
125
148
  [HeadingSize.LG]: 'text-lg',
@@ -130,6 +153,8 @@ const descriptionSizeClass = computed(() => {
130
153
 
131
154
  const spaceOvertitleClass = computed(() => {
132
155
  const map = {
156
+ [HeadingSize.XXS]: 'mb-1',
157
+ [HeadingSize.XS]: 'mb-1',
133
158
  [HeadingSize.SM]: 'mb-1',
134
159
  [HeadingSize.MD]: 'mb-3',
135
160
  [HeadingSize.LG]: 'mb-3',
@@ -140,12 +165,16 @@ const spaceOvertitleClass = computed(() => {
140
165
 
141
166
  const spaceDescriptionClass = computed(() => {
142
167
  const normal = {
168
+ [HeadingSize.XXS]: 'mt-3',
169
+ [HeadingSize.XS]: 'mt-3',
143
170
  [HeadingSize.SM]: 'mt-3',
144
171
  [HeadingSize.MD]: 'mt-4',
145
172
  [HeadingSize.LG]: 'mt-6',
146
173
  [HeadingSize.XL]: 'mt-4',
147
174
  }
148
175
  const spaced = {
176
+ [HeadingSize.XXS]: 'mt-4',
177
+ [HeadingSize.XS]: 'mt-4',
149
178
  [HeadingSize.SM]: 'mt-4',
150
179
  [HeadingSize.MD]: 'mt-5',
151
180
  [HeadingSize.LG]: 'mt-9',
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <span
3
3
  :class="[
4
- 'text-text-primary-brand-default',
4
+ colorClass,
5
5
  'text-sm',
6
6
  'font-medium',
7
7
  isUppercase && 'uppercase',
@@ -12,7 +12,7 @@
12
12
  </template>
13
13
  <script setup lang="ts">
14
14
  // Props
15
- defineProps({
15
+ const props = defineProps({
16
16
  title: {
17
17
  type: String as PropType<string>,
18
18
  default: 'Overtitle'
@@ -20,6 +20,32 @@ defineProps({
20
20
  isUppercase: {
21
21
  type: Boolean as PropType<boolean>,
22
22
  default: true,
23
+ },
24
+ color: {
25
+ type: String as PropType<
26
+ ColorAccent.PRIMARY_BRAND
27
+ | ColorAccent.SECONDARY_BRAND
28
+ | ColorAccent.NEUTRAL
29
+ >,
30
+ default: ColorAccent.SECONDARY_BRAND,
31
+ validator: (value: unknown): value is ColorAccent => {
32
+ return typeof value === 'string' &&
33
+ [
34
+ ColorAccent.PRIMARY_BRAND,
35
+ ColorAccent.SECONDARY_BRAND,
36
+ ColorAccent.NEUTRAL,
37
+ ].includes(value as ColorAccent)
38
+ },
39
+ },
40
+ })
41
+
42
+ const colorClass = computed(() => {
43
+ const variant: Partial<Record<ColorAccent, string>> = {
44
+ [ColorAccent.NEUTRAL]: 'text-text-default',
45
+ [ColorAccent.PRIMARY_BRAND]: 'text-text-primary-brand-default',
46
+ [ColorAccent.SECONDARY_BRAND]: 'text-text-secondary-brand-default',
23
47
  }
48
+
49
+ return variant[props.color as ColorAccent] || 'text-text-secondary-brand-default'
24
50
  })
25
51
  </script>
@@ -67,7 +67,6 @@ defineProps({
67
67
  default: true,
68
68
  },
69
69
  description: String as PropType<string>,
70
-
71
70
  hasGoBackLink: {
72
71
  type: Boolean as PropType<boolean>,
73
72
  default: false,
package/eslint.config.mjs CHANGED
@@ -1,15 +1,15 @@
1
- // https://eslint.nuxt.com/packages/module
2
-
3
- // @ts-check
4
- import withNuxt from './.nuxt/eslint.config.mjs'
5
-
6
- export default withNuxt({
7
- rules: {
8
- 'vue/attribute-hyphenation': 'off',
9
- 'vue/no-multiple-template-root': 'off',
10
- 'vue/require-default-prop': 'off',
11
- 'vue/multi-word-component-names': 'off',
12
- '@typescript-eslint/no-explicit-any': 'warn',
13
- '@typescript-eslint/ban-ts-comment': 'error'
14
- }
15
- })
1
+ // https://eslint.nuxt.com/packages/module
2
+
3
+ // @ts-check
4
+ import withNuxt from './.nuxt/eslint.config.mjs'
5
+
6
+ export default withNuxt({
7
+ rules: {
8
+ 'vue/attribute-hyphenation': 'off',
9
+ 'vue/no-multiple-template-root': 'off',
10
+ 'vue/require-default-prop': 'off',
11
+ 'vue/multi-word-component-names': 'off',
12
+ '@typescript-eslint/no-explicit-any': 'warn',
13
+ '@typescript-eslint/ban-ts-comment': 'error'
14
+ }
15
+ })
@@ -1,4 +1,6 @@
1
1
  export enum HeadingSize {
2
+ XXS = 'xxs',
3
+ XS = 'xs',
2
4
  SM = 'sm',
3
5
  MD = 'md',
4
6
  LG = 'lg',
@@ -3,9 +3,10 @@ export interface MenuItem {
3
3
  to: string
4
4
  }
5
5
 
6
- export interface FooterMenuItem {
7
- text: string
8
- to: string
6
+ export interface SocialNetwork {
7
+ name: string
8
+ link: string
9
+ icon: any
9
10
  }
10
11
 
11
12
  export interface SidebarMenuItem {
@@ -1,6 +1,6 @@
1
- export interface HeaderConfig {
2
- field: string
3
- callback?: (value: any) => string
4
- }
5
-
1
+ export interface HeaderConfig {
2
+ field: string
3
+ callback?: (value: any) => string
4
+ }
5
+
6
6
  export type Headers = Record<string, string | HeaderConfig>