@imaginario27/air-ui-ds 1.12.2 → 1.13.1
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.
- package/CHANGELOG.md +22 -0
- package/components/cards/specific/MetricCard.vue +211 -22
- package/components/layouts/ContentBody.vue +5 -1
- package/components/layouts/MainContent.vue +7 -3
- package/components/layouts/headers/CompactHeader.vue +19 -15
- package/components/navigation/nav-sidebar/NavSidebar.vue +7 -4
- package/components/pagination/ButtonPagination.vue +9 -9
- package/composables/useIsMobile.ts +32 -27
- package/models/enums/metrics.ts +16 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,28 @@ All notable changes to this package are documented in this file.
|
|
|
5
5
|
Historical releases were reconstructed from git history (GitHub repository) and npm publish dates.
|
|
6
6
|
Future releases will include detailed entries generated with Changesets.
|
|
7
7
|
|
|
8
|
+
## 1.13.0 - 2026-04-22
|
|
9
|
+
|
|
10
|
+
Release type: minor.
|
|
11
|
+
Commits found in range: 1.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
1. update MetricCard with new props ([4841cb3](https://github.com/imaginario27/air-ui/commit/4841cb3e6c0a8385825ff91f505b9e5ca732ca44))
|
|
16
|
+
|
|
17
|
+
- Package: @imaginario27/air-ui-ds.
|
|
18
|
+
|
|
19
|
+
## 1.12.2 - 2026-04-22
|
|
20
|
+
|
|
21
|
+
Release type: patch.
|
|
22
|
+
Commits found in range: 1.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
1. fix focus ring in form fields and icon span rendering when icon is not set ([4b0d548](https://github.com/imaginario27/air-ui/commit/4b0d5484271d25db6902eb1888a532291830fa4d))
|
|
27
|
+
|
|
28
|
+
- Package: @imaginario27/air-ui-ds.
|
|
29
|
+
|
|
8
30
|
## 1.12.1 - 2026-04-16
|
|
9
31
|
|
|
10
32
|
Release type: patch.
|
|
@@ -1,42 +1,231 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<Card>
|
|
2
|
+
<Card :class="['gap-5!', containerClass]">
|
|
3
3
|
<CardHeader>
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
<template v-if="icon">
|
|
5
|
+
<Icon
|
|
6
|
+
v-if="
|
|
7
|
+
styleType === DashboardMetricCardStyle.NEUTRAL_FILLED ||
|
|
8
|
+
styleType === DashboardMetricCardStyle.PRIMARY_BRAND_FILLED ||
|
|
9
|
+
styleType === DashboardMetricCardStyle.SECONDARY_BRAND_FILLED ||
|
|
10
|
+
styleType === DashboardMetricCardStyle.CUSTOM_FILLED
|
|
11
|
+
"
|
|
12
|
+
:name="icon"
|
|
13
|
+
:class="textColorClass"
|
|
14
|
+
/>
|
|
15
|
+
<ContainedIcon
|
|
16
|
+
v-else
|
|
17
|
+
:icon
|
|
18
|
+
:iconSize="IconSize.SM"
|
|
19
|
+
:color="iconColor"
|
|
20
|
+
:styleType="containedIconStyleType"
|
|
21
|
+
:shape="containedIconShape"
|
|
22
|
+
class="w-6! h-6! min-w-6! min-h-6!"
|
|
23
|
+
/>
|
|
24
|
+
</template>
|
|
25
|
+
|
|
26
|
+
<CardTitle
|
|
27
|
+
:title
|
|
28
|
+
:class="textColorClass"
|
|
7
29
|
/>
|
|
8
30
|
</CardHeader>
|
|
9
|
-
<CardBody>
|
|
10
|
-
<div :class="['flex
|
|
11
|
-
<span class="
|
|
12
|
-
{{
|
|
31
|
+
<CardBody class="flex flex-col! gap-2!">
|
|
32
|
+
<div :class="['flex', 'items-end', 'gap-1', textColorClass]">
|
|
33
|
+
<span class="text-3xl font-semibold">
|
|
34
|
+
{{ amount }}
|
|
13
35
|
</span>
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
36
|
+
|
|
37
|
+
<span
|
|
38
|
+
v-if="unit"
|
|
39
|
+
class="text-lg font-semibold"
|
|
17
40
|
>
|
|
41
|
+
/{{ unit }}
|
|
42
|
+
</span>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div class="flex flex-col gap-0.5">
|
|
46
|
+
<p
|
|
47
|
+
v-if="featuredDescription"
|
|
48
|
+
:class="['text-sm', 'font-semibold', textColorClass]"
|
|
49
|
+
>
|
|
50
|
+
{{ featuredDescription }}
|
|
51
|
+
</p>
|
|
52
|
+
|
|
53
|
+
<p :class="['text-sm', styleType === DashboardMetricCardStyle.DEFAULT ? 'text-text-neutral-subtle' : textColorClass]">
|
|
18
54
|
{{ description }}
|
|
19
55
|
</p>
|
|
20
56
|
</div>
|
|
57
|
+
|
|
58
|
+
<div
|
|
59
|
+
v-if="trend"
|
|
60
|
+
:class="['flex', 'items-center', 'gap-1', trendTextColorClass]"
|
|
61
|
+
>
|
|
62
|
+
<Icon
|
|
63
|
+
:name="trendIcon"
|
|
64
|
+
:size="IconSize.SM"
|
|
65
|
+
/>
|
|
66
|
+
|
|
67
|
+
<span class="text-sm mt-0.5">
|
|
68
|
+
{{ trend }}
|
|
69
|
+
</span>
|
|
70
|
+
</div>
|
|
21
71
|
</CardBody>
|
|
22
72
|
</Card>
|
|
23
73
|
</template>
|
|
74
|
+
|
|
24
75
|
<script setup lang="ts">
|
|
25
76
|
// Props
|
|
26
|
-
defineProps({
|
|
77
|
+
const props = defineProps({
|
|
78
|
+
styleType: {
|
|
79
|
+
type: String as PropType<DashboardMetricCardStyle>,
|
|
80
|
+
default: DashboardMetricCardStyle.DEFAULT,
|
|
81
|
+
validator: (value: DashboardMetricCardStyle) => Object.values(DashboardMetricCardStyle).includes(value),
|
|
82
|
+
},
|
|
27
83
|
title: {
|
|
28
84
|
type: String as PropType<string>,
|
|
29
|
-
default:
|
|
85
|
+
default: "Metric title",
|
|
86
|
+
},
|
|
87
|
+
icon: String as PropType<string>,
|
|
88
|
+
iconContainerShape: {
|
|
89
|
+
type: String as PropType<IconContainerShape>,
|
|
90
|
+
default: IconContainerShape.SQUARE,
|
|
91
|
+
validator: (value: IconContainerShape) => Object.values(IconContainerShape).includes(value),
|
|
92
|
+
},
|
|
93
|
+
defaultStyleIconColor: {
|
|
94
|
+
type: String as PropType<ColorAccent>,
|
|
95
|
+
default: ColorAccent.NEUTRAL,
|
|
96
|
+
validator: (value: ColorAccent) => Object.values(ColorAccent).includes(value),
|
|
30
97
|
},
|
|
31
|
-
|
|
98
|
+
defaultStyleIconContainerType: {
|
|
99
|
+
type: String as PropType<IconContainerStyleType>,
|
|
100
|
+
default: IconContainerStyleType.FILLED,
|
|
101
|
+
validator: (value: IconContainerStyleType) => Object.values(IconContainerStyleType).includes(value),
|
|
102
|
+
},
|
|
103
|
+
amount: {
|
|
32
104
|
type: [String, Number] as PropType<string | number>,
|
|
33
|
-
default: 0
|
|
105
|
+
default: 0,
|
|
106
|
+
},
|
|
107
|
+
unit: String as PropType<string>,
|
|
108
|
+
featuredDescription: String as PropType<string>,
|
|
109
|
+
description: {
|
|
110
|
+
type: String as PropType<string>,
|
|
111
|
+
default: "Metric description",
|
|
112
|
+
},
|
|
113
|
+
trend: String as PropType<string>,
|
|
114
|
+
trendDirection: {
|
|
115
|
+
type: String as PropType<DashboardMetricTrendDirection>,
|
|
116
|
+
default: DashboardMetricTrendDirection.UP,
|
|
117
|
+
validator: (value: DashboardMetricTrendDirection) => Object.values(DashboardMetricTrendDirection).includes(value),
|
|
34
118
|
},
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
119
|
+
customFilledColorClass: {
|
|
120
|
+
type: String as PropType<string>,
|
|
121
|
+
default: "bg-background-neutral-bold",
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Computed
|
|
126
|
+
const trendIcon = computed(() => {
|
|
127
|
+
const variant: Record<DashboardMetricTrendDirection, string> = {
|
|
128
|
+
[DashboardMetricTrendDirection.UP]: "mdi:arrow-up",
|
|
129
|
+
[DashboardMetricTrendDirection.DOWN]: "mdi:arrow-down",
|
|
130
|
+
[DashboardMetricTrendDirection.NEUTRAL]: "mdi:minus",
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
return variant[props.trendDirection as DashboardMetricTrendDirection] || "mdi:arrow-up";
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const containerClass = computed(() => {
|
|
137
|
+
const variant: Record<DashboardMetricCardStyle, string> = {
|
|
138
|
+
[DashboardMetricCardStyle.DEFAULT]: "bg-background-container-surface",
|
|
139
|
+
[DashboardMetricCardStyle.PRIMARY_BRAND_FILLED]: "bg-background-primary-brand-default border-border-primary-brand-default dark",
|
|
140
|
+
[DashboardMetricCardStyle.PRIMARY_BRAND_SOFT]: "bg-background-primary-brand-soft",
|
|
141
|
+
[DashboardMetricCardStyle.SECONDARY_BRAND_FILLED]: "bg-background-secondary-brand-default border-border-secondary-brand dark",
|
|
142
|
+
[DashboardMetricCardStyle.SECONDARY_BRAND_SOFT]: "bg-background-secondary-brand-soft",
|
|
143
|
+
[DashboardMetricCardStyle.NEUTRAL_FILLED]: "bg-background-neutral-bold dark",
|
|
144
|
+
[DashboardMetricCardStyle.NEUTRAL_SOFT]: "bg-background-neutral-subtler",
|
|
145
|
+
[DashboardMetricCardStyle.CUSTOM_FILLED]: props.customFilledColorClass,
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
return variant[props.styleType as DashboardMetricCardStyle] || "bg-background-container-surface";
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const containedIconStyleType = computed(() => {
|
|
152
|
+
if (props.styleType === DashboardMetricCardStyle.DEFAULT) {
|
|
153
|
+
return props.defaultStyleIconContainerType;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return IconContainerStyleType.FILLED;
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const containedIconShape = computed(() => {
|
|
160
|
+
if (props.styleType === DashboardMetricCardStyle.DEFAULT) {
|
|
161
|
+
return props.iconContainerShape;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return IconContainerShape.SQUARE;
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const iconColor = computed(() => {
|
|
168
|
+
if (props.defaultStyleIconColor && props.styleType === DashboardMetricCardStyle.DEFAULT) {
|
|
169
|
+
return props.defaultStyleIconColor;
|
|
40
170
|
}
|
|
41
|
-
|
|
42
|
-
|
|
171
|
+
|
|
172
|
+
const variant: Record<DashboardMetricCardStyle, ColorAccent> = {
|
|
173
|
+
[DashboardMetricCardStyle.DEFAULT]: ColorAccent.NEUTRAL,
|
|
174
|
+
[DashboardMetricCardStyle.PRIMARY_BRAND_FILLED]: ColorAccent.PRIMARY_BRAND,
|
|
175
|
+
[DashboardMetricCardStyle.PRIMARY_BRAND_SOFT]: ColorAccent.PRIMARY_BRAND,
|
|
176
|
+
[DashboardMetricCardStyle.SECONDARY_BRAND_FILLED]: ColorAccent.SECONDARY_BRAND,
|
|
177
|
+
[DashboardMetricCardStyle.SECONDARY_BRAND_SOFT]: ColorAccent.SECONDARY_BRAND,
|
|
178
|
+
[DashboardMetricCardStyle.NEUTRAL_FILLED]: ColorAccent.NEUTRAL,
|
|
179
|
+
[DashboardMetricCardStyle.NEUTRAL_SOFT]: ColorAccent.NEUTRAL,
|
|
180
|
+
[DashboardMetricCardStyle.CUSTOM_FILLED]: ColorAccent.NEUTRAL,
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
return variant[props.styleType as DashboardMetricCardStyle] || ColorAccent.NEUTRAL;
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const textColorClass = computed(() => {
|
|
187
|
+
const variant: Record<DashboardMetricCardStyle, string> = {
|
|
188
|
+
[DashboardMetricCardStyle.DEFAULT]: "text-text-neutral-default",
|
|
189
|
+
[DashboardMetricCardStyle.PRIMARY_BRAND_FILLED]: "text-text-neutral-on-filled",
|
|
190
|
+
[DashboardMetricCardStyle.PRIMARY_BRAND_SOFT]: "text-text-primary-brand-on-soft-bg",
|
|
191
|
+
[DashboardMetricCardStyle.SECONDARY_BRAND_FILLED]: "text-text-neutral-on-filled",
|
|
192
|
+
[DashboardMetricCardStyle.SECONDARY_BRAND_SOFT]: "text-text-secondary-brand-on-soft-bg",
|
|
193
|
+
[DashboardMetricCardStyle.NEUTRAL_FILLED]: "text-text-neutral-on-filled",
|
|
194
|
+
[DashboardMetricCardStyle.NEUTRAL_SOFT]: "text-text-neutral-on-neutral-bg",
|
|
195
|
+
[DashboardMetricCardStyle.CUSTOM_FILLED]: "text-text-neutral-on-filled",
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
return variant[props.styleType as DashboardMetricCardStyle] || "text-text-neutral-default";
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const isFilledStyle = computed(() => {
|
|
202
|
+
return [
|
|
203
|
+
DashboardMetricCardStyle.PRIMARY_BRAND_FILLED,
|
|
204
|
+
DashboardMetricCardStyle.SECONDARY_BRAND_FILLED,
|
|
205
|
+
DashboardMetricCardStyle.NEUTRAL_FILLED,
|
|
206
|
+
DashboardMetricCardStyle.CUSTOM_FILLED,
|
|
207
|
+
].includes(props.styleType as DashboardMetricCardStyle);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const trendUpClass = computed(() => {
|
|
211
|
+
return isFilledStyle.value ? "text-text-neutral-on-filled" : "text-text-success";
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const trendDownClass = computed(() => {
|
|
215
|
+
return isFilledStyle.value ? "text-text-neutral-on-filled" : "text-text-error";
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const trendNeutralClass = computed(() => {
|
|
219
|
+
return isFilledStyle.value ? "text-text-neutral-on-filled" : "text-text-neutral-subtle";
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const trendTextColorClass = computed(() => {
|
|
223
|
+
const variant: Record<DashboardMetricTrendDirection, string> = {
|
|
224
|
+
[DashboardMetricTrendDirection.UP]: trendUpClass.value,
|
|
225
|
+
[DashboardMetricTrendDirection.DOWN]: trendDownClass.value,
|
|
226
|
+
[DashboardMetricTrendDirection.NEUTRAL]: trendNeutralClass.value,
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
return variant[props.trendDirection as DashboardMetricTrendDirection] || trendUpClass.value;
|
|
230
|
+
});
|
|
231
|
+
</script>
|
|
@@ -20,11 +20,15 @@ const props = defineProps({
|
|
|
20
20
|
type: Number as PropType<number>,
|
|
21
21
|
default: 240,
|
|
22
22
|
},
|
|
23
|
+
mobileBreakpoint: {
|
|
24
|
+
type: Number as PropType<number>,
|
|
25
|
+
default: 1024,
|
|
26
|
+
},
|
|
23
27
|
})
|
|
24
28
|
|
|
25
29
|
// Composables
|
|
26
30
|
const { isMobileSidebarOpen } = useMobileSidebar()
|
|
27
|
-
const { isMobile } = useIsMobile()
|
|
31
|
+
const { isMobile } = useIsMobile(() => props.mobileBreakpoint)
|
|
28
32
|
|
|
29
33
|
// Computed dynamic style
|
|
30
34
|
const containerStyle = computed(() => {
|
|
@@ -15,13 +15,17 @@
|
|
|
15
15
|
|
|
16
16
|
<script setup lang="ts">
|
|
17
17
|
// Props
|
|
18
|
-
defineProps({
|
|
18
|
+
const props = defineProps({
|
|
19
19
|
tocSidebarWidth: {
|
|
20
20
|
type: Number as PropType<number>,
|
|
21
21
|
default: 240
|
|
22
|
-
}
|
|
22
|
+
},
|
|
23
|
+
mobileBreakpoint: {
|
|
24
|
+
type: Number as PropType<number>,
|
|
25
|
+
default: 1024,
|
|
26
|
+
},
|
|
23
27
|
})
|
|
24
28
|
|
|
25
29
|
// Composable
|
|
26
|
-
const { isMobile } = useIsMobile()
|
|
30
|
+
const { isMobile } = useIsMobile(() => props.mobileBreakpoint)
|
|
27
31
|
</script>
|
|
@@ -34,12 +34,12 @@
|
|
|
34
34
|
&& sidebarTogglePosition === SidebarTogglePosition.LOGO_LEFT_SIDE
|
|
35
35
|
"
|
|
36
36
|
>
|
|
37
|
-
<ActionIconButton
|
|
37
|
+
<ActionIconButton
|
|
38
38
|
:icon="isMobileSidebarOpen ? 'mdi:menu-open' : 'mdi:menu-close'"
|
|
39
|
-
class="
|
|
39
|
+
:class="['shadow-sm', !isMobile && 'hidden']"
|
|
40
40
|
@click="toggleMobileSidebar"
|
|
41
41
|
/>
|
|
42
|
-
</template>
|
|
42
|
+
</template>
|
|
43
43
|
|
|
44
44
|
<slot name="header-logo" />
|
|
45
45
|
|
|
@@ -49,20 +49,20 @@
|
|
|
49
49
|
&& sidebarTogglePosition === SidebarTogglePosition.LOGO_RIGHT_SIDE
|
|
50
50
|
"
|
|
51
51
|
>
|
|
52
|
-
<ActionIconButton
|
|
52
|
+
<ActionIconButton
|
|
53
53
|
:icon="isMobileSidebarOpen ? 'mdi:menu-open' : 'mdi:menu-close'"
|
|
54
|
-
class="
|
|
54
|
+
:class="['shadow-sm', !isMobile && 'hidden']"
|
|
55
55
|
@click="toggleMobileSidebar"
|
|
56
56
|
/>
|
|
57
|
-
</template>
|
|
57
|
+
</template>
|
|
58
58
|
</div>
|
|
59
59
|
|
|
60
60
|
<!-- Navigation -->
|
|
61
61
|
<div class="flex gap-3 items-center xs:w-auto">
|
|
62
62
|
<!-- Horizontal menu -->
|
|
63
|
-
<NavMenu
|
|
64
|
-
v-if="navMenuItems.length"
|
|
65
|
-
:menuItems="navMenuItems"
|
|
63
|
+
<NavMenu
|
|
64
|
+
v-if="navMenuItems.length && !isMobile"
|
|
65
|
+
:menuItems="navMenuItems"
|
|
66
66
|
:detectActive="detectActiveMenuItem"
|
|
67
67
|
:submenuYOffset
|
|
68
68
|
:submenuDropdownClass
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
<!-- Header actions -->
|
|
75
75
|
<div
|
|
76
76
|
v-if="$slots['header-actions']"
|
|
77
|
-
class="gap-3 items-center hidden
|
|
77
|
+
:class="['gap-3 items-center', isMobile ? 'hidden' : 'flex']"
|
|
78
78
|
>
|
|
79
79
|
<slot name="header-actions" />
|
|
80
80
|
</div>
|
|
@@ -117,9 +117,9 @@
|
|
|
117
117
|
:positionYOffset="submenuYOffset"
|
|
118
118
|
>
|
|
119
119
|
<template #activator>
|
|
120
|
-
<ActionIconButton
|
|
120
|
+
<ActionIconButton
|
|
121
121
|
icon="mdi:menu"
|
|
122
|
-
class="
|
|
122
|
+
class="shadow-sm"
|
|
123
123
|
/>
|
|
124
124
|
</template>
|
|
125
125
|
<template #items>
|
|
@@ -197,6 +197,10 @@ const props = defineProps({
|
|
|
197
197
|
type: Array as PropType<DropdownMenuItem[]>,
|
|
198
198
|
default: () => [],
|
|
199
199
|
},
|
|
200
|
+
mobileBreakpoint: {
|
|
201
|
+
type: Number as PropType<number>,
|
|
202
|
+
default: 1024,
|
|
203
|
+
},
|
|
200
204
|
showMobileMenuToggle: {
|
|
201
205
|
type: Boolean as PropType<boolean>,
|
|
202
206
|
default: true,
|
|
@@ -232,18 +236,18 @@ const props = defineProps({
|
|
|
232
236
|
},
|
|
233
237
|
navMenuClass: {
|
|
234
238
|
type: String as PropType<string>,
|
|
235
|
-
default: '
|
|
239
|
+
default: ''
|
|
236
240
|
},
|
|
237
241
|
navMobileMenuClass: {
|
|
238
242
|
type: String as PropType<string>,
|
|
239
|
-
default: '
|
|
243
|
+
default: 'min-w-[280px]'
|
|
240
244
|
},
|
|
241
245
|
headerClass: String as PropType<string>,
|
|
242
246
|
})
|
|
243
247
|
|
|
244
248
|
// Composables
|
|
245
249
|
const { isMobileSidebarOpen, toggleMobileSidebar } = useMobileSidebar()
|
|
246
|
-
const { isMobile } = useIsMobile()
|
|
250
|
+
const { isMobile } = useIsMobile(() => props.mobileBreakpoint)
|
|
247
251
|
|
|
248
252
|
const getSubmenuItems = (item: MenuItem): NonNullable<MenuItem['children']> => {
|
|
249
253
|
return item.children ?? []
|
|
@@ -14,8 +14,7 @@
|
|
|
14
14
|
'py-4',
|
|
15
15
|
'border-r border-border-default',
|
|
16
16
|
'transition-all duration-300 ease-in-out',
|
|
17
|
-
isMobileSidebarOpen ? 'translate-x-0' : '-translate-x-full',
|
|
18
|
-
'lg:translate-x-0', // Always visible on large screens
|
|
17
|
+
(!isMobile || isMobileSidebarOpen) ? 'translate-x-0' : '-translate-x-full',
|
|
19
18
|
]"
|
|
20
19
|
>
|
|
21
20
|
<!-- Collapse & Close Buttons -->
|
|
@@ -32,7 +31,7 @@
|
|
|
32
31
|
<ActionIconButton
|
|
33
32
|
v-if="showMobileSidebarClose && isMobile && !isCollapsed"
|
|
34
33
|
:icon="mobileSidebarCloseIcon"
|
|
35
|
-
class="flex
|
|
34
|
+
class="flex"
|
|
36
35
|
:size="ButtonSize.SM"
|
|
37
36
|
@click="toggleMobileSidebar()"
|
|
38
37
|
/>
|
|
@@ -294,6 +293,10 @@ const props = defineProps({
|
|
|
294
293
|
type: String as PropType<string>,
|
|
295
294
|
default: 'mdi:close-circle',
|
|
296
295
|
},
|
|
296
|
+
mobileBreakpoint: {
|
|
297
|
+
type: Number as PropType<number>,
|
|
298
|
+
default: 1024,
|
|
299
|
+
},
|
|
297
300
|
isFixed: {
|
|
298
301
|
type: Boolean as PropType<boolean>,
|
|
299
302
|
default: true,
|
|
@@ -354,7 +357,7 @@ const {
|
|
|
354
357
|
setSidebarCollapsed,
|
|
355
358
|
toggleSidebarState,
|
|
356
359
|
} = useSidebar()
|
|
357
|
-
const { isMobile } = useIsMobile()
|
|
360
|
+
const { isMobile } = useIsMobile(() => props.mobileBreakpoint)
|
|
358
361
|
|
|
359
362
|
const MAX_NESTING_LEVEL = 3
|
|
360
363
|
|
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
'justify-between',
|
|
6
6
|
'items-center',
|
|
7
7
|
'gap-3',
|
|
8
|
-
'flex-col-reverse',
|
|
9
|
-
'lg:flex-row',
|
|
8
|
+
isMobile ? 'flex-col-reverse' : 'flex-row',
|
|
10
9
|
'w-full'
|
|
11
10
|
]"
|
|
12
11
|
>
|
|
@@ -26,7 +25,7 @@
|
|
|
26
25
|
v-model="itemsPerPage"
|
|
27
26
|
:rowsLabel="rowsPerPageLabel"
|
|
28
27
|
:rowsOptions="rowsPerPageOptions"
|
|
29
|
-
class="flex
|
|
28
|
+
:class="[isMobile ? 'flex' : 'hidden']"
|
|
30
29
|
/> <!-- Mobile position -->
|
|
31
30
|
</div>
|
|
32
31
|
|
|
@@ -35,10 +34,7 @@
|
|
|
35
34
|
:class="[
|
|
36
35
|
'flex',
|
|
37
36
|
'gap-6',
|
|
38
|
-
'flex-col'
|
|
39
|
-
'w-full',
|
|
40
|
-
'lg:flex-row',
|
|
41
|
-
'lg:w-auto'
|
|
37
|
+
isMobile ? 'flex-col w-full' : 'flex-row w-auto'
|
|
42
38
|
]"
|
|
43
39
|
>
|
|
44
40
|
<RowsPerPage
|
|
@@ -46,7 +42,7 @@
|
|
|
46
42
|
v-model="itemsPerPage"
|
|
47
43
|
:rowsLabel="rowsPerPageLabel"
|
|
48
44
|
:rowsOptions="rowsPerPageOptions"
|
|
49
|
-
class="hidden
|
|
45
|
+
:class="[isMobile ? 'hidden' : 'flex']"
|
|
50
46
|
/>
|
|
51
47
|
<nav
|
|
52
48
|
v-if="totalItems > itemsPerPage"
|
|
@@ -168,10 +164,14 @@ const props = defineProps({
|
|
|
168
164
|
type: String as PropType<string>,
|
|
169
165
|
default: 'Showing {total} result',
|
|
170
166
|
},
|
|
167
|
+
mobileBreakpoint: {
|
|
168
|
+
type: Number as PropType<number>,
|
|
169
|
+
default: 1024,
|
|
170
|
+
},
|
|
171
171
|
})
|
|
172
172
|
|
|
173
173
|
// Composables
|
|
174
|
-
const { isMobile } = useIsMobile()
|
|
174
|
+
const { isMobile } = useIsMobile(() => props.mobileBreakpoint)
|
|
175
175
|
|
|
176
176
|
// States
|
|
177
177
|
const itemsPerPage = ref(props.itemsPerPage)
|
|
@@ -1,27 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
1
|
+
import { toValue, type MaybeRefOrGetter } from 'vue'
|
|
2
|
+
|
|
3
|
+
export const useIsMobile = (breakpoint: MaybeRefOrGetter<number> = 1024) => {
|
|
4
|
+
const isMobile = ref(false) // Initial value for SSR
|
|
5
|
+
|
|
6
|
+
// Function to update `isMobile`
|
|
7
|
+
const updateIsMobile = () => {
|
|
8
|
+
if (typeof window !== 'undefined') {
|
|
9
|
+
isMobile.value = window.innerWidth < toValue(breakpoint)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Attach resize listener on the client
|
|
14
|
+
onMounted(() => {
|
|
15
|
+
updateIsMobile() // Initialize the value
|
|
16
|
+
window.addEventListener('resize', updateIsMobile)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
// Re-evaluate when the breakpoint itself changes
|
|
20
|
+
watch(() => toValue(breakpoint), updateIsMobile)
|
|
21
|
+
|
|
22
|
+
// Cleanup listener when unmounted
|
|
23
|
+
onUnmounted(() => {
|
|
24
|
+
if (typeof window !== 'undefined') {
|
|
25
|
+
window.removeEventListener('resize', updateIsMobile)
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
isMobile,
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export enum DashboardMetricCardStyle {
|
|
2
|
+
DEFAULT = "default",
|
|
3
|
+
PRIMARY_BRAND_FILLED = "primary-brand-filled",
|
|
4
|
+
PRIMARY_BRAND_SOFT = "primary-brand-soft",
|
|
5
|
+
SECONDARY_BRAND_FILLED = "secondary-brand-filled",
|
|
6
|
+
SECONDARY_BRAND_SOFT = "secondary-brand-soft",
|
|
7
|
+
NEUTRAL_FILLED = "neutral-filled",
|
|
8
|
+
NEUTRAL_SOFT = "neutral-soft",
|
|
9
|
+
CUSTOM_FILLED = "custom-filled",
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export enum DashboardMetricTrendDirection {
|
|
13
|
+
UP = "up",
|
|
14
|
+
DOWN = "down",
|
|
15
|
+
NEUTRAL = "neutral",
|
|
16
|
+
}
|