@nhealth/nutils 0.0.2

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 (36) hide show
  1. package/README.md +503 -0
  2. package/dist/module.cjs +43 -0
  3. package/dist/module.d.cts +13 -0
  4. package/dist/module.d.mts +13 -0
  5. package/dist/module.d.ts +13 -0
  6. package/dist/module.json +12 -0
  7. package/dist/module.mjs +40 -0
  8. package/dist/runtime/app/assets/styles.css +1 -0
  9. package/dist/runtime/app/components/ComponentRouter.d.vue.ts +50 -0
  10. package/dist/runtime/app/components/ComponentRouter.vue +26 -0
  11. package/dist/runtime/app/components/ComponentRouter.vue.d.ts +50 -0
  12. package/dist/runtime/app/components/ComponentShell.d.vue.ts +28 -0
  13. package/dist/runtime/app/components/ComponentShell.vue +95 -0
  14. package/dist/runtime/app/components/ComponentShell.vue.d.ts +28 -0
  15. package/dist/runtime/app/components/ConfirmModal.d.vue.ts +3 -0
  16. package/dist/runtime/app/components/ConfirmModal.vue +163 -0
  17. package/dist/runtime/app/components/ConfirmModal.vue.d.ts +3 -0
  18. package/dist/runtime/app/components/LiveIndicator.d.vue.ts +9 -0
  19. package/dist/runtime/app/components/LiveIndicator.vue +43 -0
  20. package/dist/runtime/app/components/LiveIndicator.vue.d.ts +9 -0
  21. package/dist/runtime/app/components/StatCard.d.vue.ts +15 -0
  22. package/dist/runtime/app/components/StatCard.vue +99 -0
  23. package/dist/runtime/app/components/StatCard.vue.d.ts +15 -0
  24. package/dist/runtime/app/components/StatCounter.d.vue.ts +11 -0
  25. package/dist/runtime/app/components/StatCounter.vue +29 -0
  26. package/dist/runtime/app/components/StatCounter.vue.d.ts +11 -0
  27. package/dist/runtime/app/composables/useComponentRouter.d.ts +50 -0
  28. package/dist/runtime/app/composables/useComponentRouter.js +265 -0
  29. package/dist/runtime/app/composables/useConfirmModal.d.ts +50 -0
  30. package/dist/runtime/app/composables/useConfirmModal.js +107 -0
  31. package/dist/runtime/app/composables/useConfirmModalState.d.ts +12 -0
  32. package/dist/runtime/app/composables/useConfirmModalState.js +12 -0
  33. package/dist/runtime/shared/formatters.d.ts +22 -0
  34. package/dist/runtime/shared/formatters.js +15 -0
  35. package/dist/types.d.mts +3 -0
  36. package/package.json +63 -0
@@ -0,0 +1,26 @@
1
+ <template>
2
+ <slot
3
+ :component="component"
4
+ :route="route"
5
+ :push="push"
6
+ />
7
+ </template>
8
+
9
+ <script setup>
10
+ import { useComponentRouter } from "#imports";
11
+ const props = defineProps({
12
+ routes: { type: [Array, Object], required: true },
13
+ base: { type: String, required: false, default: "fp" },
14
+ mode: { type: String, required: false, default: "query" },
15
+ initial: { type: String, required: false },
16
+ debug: { type: Boolean, required: false, default: false }
17
+ });
18
+ const { component, route, push, replace } = useComponentRouter({
19
+ routes: props.routes,
20
+ base: props.base,
21
+ mode: props.mode,
22
+ initial: props.initial,
23
+ debug: props.debug
24
+ });
25
+ defineExpose({ push, replace, route });
26
+ </script>
@@ -0,0 +1,50 @@
1
+ import { type Component } from '#imports';
2
+ type AsyncComponentLoader = () => Promise<Component | {
3
+ default: Component;
4
+ }>;
5
+ type ComponentRouteRecord = {
6
+ path: string;
7
+ component: Component | AsyncComponentLoader;
8
+ name?: string;
9
+ };
10
+ type ComponentRouterMode = 'query' | 'hash' | 'memory';
11
+ type __VLS_Props = {
12
+ routes: ComponentRouteRecord[] | Record<string, Component | AsyncComponentLoader>;
13
+ base?: string;
14
+ mode?: ComponentRouterMode;
15
+ initial?: string;
16
+ debug?: boolean;
17
+ };
18
+ declare var __VLS_1: {
19
+ component: Component;
20
+ route: {
21
+ path: string;
22
+ params: Record<string, string>;
23
+ query: Record<string, string | string[]>;
24
+ };
25
+ push: (path: string) => Promise<void>;
26
+ };
27
+ type __VLS_Slots = {} & {
28
+ default?: (props: typeof __VLS_1) => any;
29
+ };
30
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
31
+ push: (path: string) => Promise<void>;
32
+ replace: (path: string) => Promise<void>;
33
+ route: import("vue").ShallowRef<{
34
+ path: string;
35
+ params: Record<string, string>;
36
+ query: Record<string, string | string[]>;
37
+ }>;
38
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
39
+ base: string;
40
+ mode: ComponentRouterMode;
41
+ debug: boolean;
42
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
43
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
44
+ declare const _default: typeof __VLS_export;
45
+ export default _default;
46
+ type __VLS_WithSlots<T, S> = T & {
47
+ new (): {
48
+ $slots: S;
49
+ };
50
+ };
@@ -0,0 +1,28 @@
1
+ import type { NavigationMenuItem } from '@nuxt/ui';
2
+ type __VLS_Props = {
3
+ orientation?: 'horizontal' | 'vertical';
4
+ items?: NavigationMenuItem[][];
5
+ activeMatch?: 'exact' | 'prefix';
6
+ pageOffset?: string | number;
7
+ };
8
+ declare var __VLS_1: {}, __VLS_8: {}, __VLS_10: {};
9
+ type __VLS_Slots = {} & {
10
+ leading?: (props: typeof __VLS_1) => any;
11
+ } & {
12
+ trailing?: (props: typeof __VLS_8) => any;
13
+ } & {
14
+ default?: (props: typeof __VLS_10) => any;
15
+ };
16
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
17
+ orientation: "horizontal" | "vertical";
18
+ activeMatch: "exact" | "prefix";
19
+ pageOffset: string | number;
20
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
21
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
22
+ declare const _default: typeof __VLS_export;
23
+ export default _default;
24
+ type __VLS_WithSlots<T, S> = T & {
25
+ new (): {
26
+ $slots: S;
27
+ };
28
+ };
@@ -0,0 +1,95 @@
1
+ <template>
2
+ <section class="h-full">
3
+ <!-- Layout container switches between column (horizontal nav top) and row (vertical nav left) -->
4
+ <div
5
+ :class="
6
+ orientation === 'vertical' ? 'flex h-full' : 'flex flex-col w-full'
7
+ "
8
+ :style="containerStyle"
9
+ >
10
+ <!-- Navigation using UNavigationMenu -->
11
+ <div
12
+ :class="[
13
+ 'border-gray-200',
14
+ orientation === 'vertical' ? 'flex flex-col min-w-[256px] border-r' : 'flex border-b'
15
+ ]"
16
+ >
17
+ <slot name="leading" />
18
+ <UNavigationMenu
19
+ :items="navigationItems"
20
+ :orientation="orientation"
21
+ content-orientation="vertical"
22
+ :class="orientation === 'horizontal' ? 'flex-1 px-4' : 'flex-1 py-4 px-2'"
23
+ />
24
+ <slot name="trailing" />
25
+ </div>
26
+
27
+ <!-- Main Content -->
28
+ <div
29
+ class="flex-1 min-h-0 overflow-y-auto overflow-x-hidden"
30
+ >
31
+ <slot />
32
+ </div>
33
+ </div>
34
+ </section>
35
+ </template>
36
+
37
+ <script setup>
38
+ import { computed } from "vue";
39
+ import { useComponentRouter } from "#imports";
40
+ const props = defineProps({
41
+ orientation: { type: String, required: false, default: "horizontal" },
42
+ items: { type: Array, required: false },
43
+ activeMatch: { type: String, required: false, default: "prefix" },
44
+ pageOffset: { type: [String, Number], required: false, default: 0 }
45
+ });
46
+ const router = useComponentRouter();
47
+ const containerStyle = computed(() => {
48
+ const offset = props.pageOffset;
49
+ const offsetValue = typeof offset === "string" ? offset : `${offset}px`;
50
+ return `height: calc(100vh - ${offsetValue})`;
51
+ });
52
+ const navigationItems = computed(() => {
53
+ if (!props.items) return [];
54
+ const transformItem = (item) => {
55
+ const path = item.path;
56
+ const transformed = {
57
+ ...item,
58
+ onSelect: (e) => {
59
+ e.preventDefault();
60
+ if (path) {
61
+ router.push(path);
62
+ }
63
+ if (item.onSelect) {
64
+ item.onSelect(e);
65
+ }
66
+ },
67
+ active: path ? isActive(path) : item.active
68
+ };
69
+ if (item.children) {
70
+ if (Array.isArray(item.children[0]) && Array.isArray(item.children[0][0])) {
71
+ transformed.children = item.children.map(
72
+ (childGroup) => childGroup.map(transformItem)
73
+ );
74
+ } else {
75
+ transformed.children = item.children.map(transformItem);
76
+ }
77
+ }
78
+ return transformed;
79
+ };
80
+ return props.items.map(
81
+ (group) => group.map(transformItem)
82
+ );
83
+ });
84
+ function isActive(path) {
85
+ if (!path) return false;
86
+ const current = router.route?.value?.path || "";
87
+ if (props.activeMatch === "prefix") return isPrefixActive(current, path);
88
+ return current === path;
89
+ }
90
+ function isPrefixActive(current, base) {
91
+ if (base === "/") return current === "/";
92
+ if (current === base) return true;
93
+ return current.startsWith(base.endsWith("/") ? base : base + "/");
94
+ }
95
+ </script>
@@ -0,0 +1,28 @@
1
+ import type { NavigationMenuItem } from '@nuxt/ui';
2
+ type __VLS_Props = {
3
+ orientation?: 'horizontal' | 'vertical';
4
+ items?: NavigationMenuItem[][];
5
+ activeMatch?: 'exact' | 'prefix';
6
+ pageOffset?: string | number;
7
+ };
8
+ declare var __VLS_1: {}, __VLS_8: {}, __VLS_10: {};
9
+ type __VLS_Slots = {} & {
10
+ leading?: (props: typeof __VLS_1) => any;
11
+ } & {
12
+ trailing?: (props: typeof __VLS_8) => any;
13
+ } & {
14
+ default?: (props: typeof __VLS_10) => any;
15
+ };
16
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
17
+ orientation: "horizontal" | "vertical";
18
+ activeMatch: "exact" | "prefix";
19
+ pageOffset: string | number;
20
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
21
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
22
+ declare const _default: typeof __VLS_export;
23
+ export default _default;
24
+ type __VLS_WithSlots<T, S> = T & {
25
+ new (): {
26
+ $slots: S;
27
+ };
28
+ };
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,163 @@
1
+ <template>
2
+ <UModal
3
+ v-model:open="state.open"
4
+ :ui="{ content: 'max-w-md' }"
5
+ @update:open="onOpenUpdate"
6
+ >
7
+ <!-- Header with Icon -->
8
+ <template #header>
9
+ <div class="flex items-start gap-3">
10
+ <div
11
+ v-if="opts.icon"
12
+ class="shrink-0 rounded-full p-2 w-10 h-10 flex items-center justify-center"
13
+ :class="iconColorClasses"
14
+ >
15
+ <UIcon
16
+ :name="opts.icon"
17
+ class="size-5"
18
+ />
19
+ </div>
20
+ <div class="flex-1 min-w-0 self-center">
21
+ <h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">
22
+ {{ opts.title || "Confirm" }}
23
+ </h3>
24
+ </div>
25
+ </div>
26
+ </template>
27
+
28
+ <!-- Body -->
29
+ <template #body>
30
+ <div class="space-y-4">
31
+ <!-- Description or text -->
32
+ <div
33
+ v-if="opts.description || opts.text"
34
+ class="space-y-2"
35
+ >
36
+ <p class="text-sm text-gray-600 dark:text-gray-400">
37
+ {{ opts.description || opts.text }}
38
+ </p>
39
+ </div>
40
+
41
+ <!-- Bullet list items -->
42
+ <ul
43
+ v-if="opts.items && opts.items.length > 0"
44
+ class="space-y-2 text-sm text-gray-600 dark:text-gray-400"
45
+ >
46
+ <li
47
+ v-for="(item, index) in opts.items"
48
+ :key="index"
49
+ class="flex items-start gap-2"
50
+ >
51
+ <UIcon
52
+ name="i-lucide-circle"
53
+ class="size-1.5 mt-1.5 shrink-0"
54
+ />
55
+ <span>{{ item }}</span>
56
+ </li>
57
+ </ul>
58
+
59
+ <!-- Warning message -->
60
+ <p
61
+ v-if="opts.warning"
62
+ class="text-sm font-medium text-amber-600 dark:text-amber-400"
63
+ >
64
+ {{ opts.warning }}
65
+ </p>
66
+
67
+ <!-- Input validation gate -->
68
+ <div v-if="opts.requireInputEquals">
69
+ <UFormField
70
+ :label="opts.inputLabel || 'Type to confirm'"
71
+ :help="inputHelp"
72
+ >
73
+ <UInput
74
+ v-model="state.confirmInput"
75
+ :placeholder="opts.inputPlaceholder || ''"
76
+ />
77
+ </UFormField>
78
+ </div>
79
+
80
+ <!-- Checkbox validation gate -->
81
+ <div v-if="opts.requireCheckbox">
82
+ <UCheckbox
83
+ v-model="state.confirmChecked"
84
+ :label="opts.checkboxLabel || 'I understand and want to continue'"
85
+ />
86
+ </div>
87
+ </div>
88
+ </template>
89
+
90
+ <!-- Footer with actions -->
91
+ <template #footer>
92
+ <div class="flex justify-end gap-2">
93
+ <UButton
94
+ :label="opts.cancelLabel || 'Cancel'"
95
+ :color="opts.cancelColor || 'neutral'"
96
+ :variant="opts.cancelVariant || 'subtle'"
97
+ :autofocus="opts.autofocus === 'cancel'"
98
+ :disabled="state.loading"
99
+ @click="actions.onCancel()"
100
+ />
101
+
102
+ <UButton
103
+ :label="opts.confirmLabel || 'Confirm'"
104
+ :color="(opts.dangerous ? 'error' : opts.confirmColor) || 'primary'"
105
+ :variant="opts.confirmVariant || 'solid'"
106
+ :disabled="confirmDisabled"
107
+ :autofocus="opts.autofocus === 'confirm'"
108
+ :loading="state.loading"
109
+ @click="actions.onConfirm()"
110
+ />
111
+ </div>
112
+ </template>
113
+ </UModal>
114
+ </template>
115
+
116
+ <script setup>
117
+ import { computed } from "vue";
118
+ const state = useConfirmModalState();
119
+ const actions = useConfirmModalActions();
120
+ const opts = computed(() => {
121
+ const source = state.value.current?.options || state.value.renderOptions;
122
+ return source || {};
123
+ });
124
+ const iconColorClasses = computed(() => {
125
+ switch (opts.value.iconColor) {
126
+ case "error":
127
+ return "bg-red-50 dark:bg-red-950/50 text-red-600 dark:text-red-400";
128
+ case "warning":
129
+ return "bg-amber-50 dark:bg-amber-950/50 text-amber-600 dark:text-amber-400";
130
+ case "success":
131
+ return "bg-emerald-50 dark:bg-emerald-950/50 text-emerald-600 dark:text-emerald-400";
132
+ case "info":
133
+ return "bg-blue-50 dark:bg-blue-950/50 text-blue-600 dark:text-blue-400";
134
+ default:
135
+ return "bg-primary-50 dark:bg-primary-950/50 text-primary-600 dark:text-primary-400";
136
+ }
137
+ });
138
+ const inputHelp = computed(() => {
139
+ if (opts.value?.inputHint)
140
+ return opts.value.inputHint;
141
+ if (opts.value?.requireInputEquals) {
142
+ return `Type "${opts.value.requireInputEquals}" to enable confirm`;
143
+ }
144
+ return void 0;
145
+ });
146
+ const confirmDisabled = computed(() => {
147
+ const o = opts.value;
148
+ if (!o)
149
+ return true;
150
+ if (o.requireInputEquals && state.value.confirmInput !== o.requireInputEquals)
151
+ return true;
152
+ if (o.requireCheckbox && !state.value.confirmChecked)
153
+ return true;
154
+ if (state.value.loading)
155
+ return true;
156
+ return false;
157
+ });
158
+ function onOpenUpdate(v) {
159
+ if (!v && state.value.current) {
160
+ actions.onClosedExternally();
161
+ }
162
+ }
163
+ </script>
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,9 @@
1
+ type Status = 'connected' | 'reconnecting' | 'offline';
2
+ interface Props {
3
+ status?: Status;
4
+ }
5
+ declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {
6
+ status: Status;
7
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
8
+ declare const _default: typeof __VLS_export;
9
+ export default _default;
@@ -0,0 +1,43 @@
1
+ <template>
2
+ <div
3
+ class="inline-flex items-center gap-2 px-2.5 py-1.5 rounded-full text-xs font-medium transition-colors"
4
+ :class="statusClasses"
5
+ >
6
+ <div
7
+ class="w-2 h-2 rounded-full"
8
+ :class="dotClasses"
9
+ />
10
+ <span>{{ statusText }}</span>
11
+ </div>
12
+ </template>
13
+
14
+ <script setup>
15
+ import { computed } from "vue";
16
+ const props = defineProps({
17
+ status: { type: String, required: false, default: "offline" }
18
+ });
19
+ const statusClasses = computed(() => {
20
+ const classes = {
21
+ connected: "bg-emerald-50 dark:bg-emerald-900/30 text-emerald-700 dark:text-emerald-300",
22
+ reconnecting: "bg-amber-50 dark:bg-amber-900/30 text-amber-700 dark:text-amber-300",
23
+ offline: "bg-gray-100 dark:bg-gray-900 text-gray-600 dark:text-gray-400"
24
+ };
25
+ return classes[props.status];
26
+ });
27
+ const dotClasses = computed(() => {
28
+ const classes = {
29
+ connected: "bg-emerald-500 animate-pulse",
30
+ reconnecting: "bg-amber-500 animate-pulse",
31
+ offline: "bg-gray-400"
32
+ };
33
+ return classes[props.status];
34
+ });
35
+ const statusText = computed(() => {
36
+ const texts = {
37
+ connected: "Live",
38
+ reconnecting: "Reconnecting",
39
+ offline: "Offline"
40
+ };
41
+ return texts[props.status];
42
+ });
43
+ </script>
@@ -0,0 +1,9 @@
1
+ type Status = 'connected' | 'reconnecting' | 'offline';
2
+ interface Props {
3
+ status?: Status;
4
+ }
5
+ declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {
6
+ status: Status;
7
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
8
+ declare const _default: typeof __VLS_export;
9
+ export default _default;
@@ -0,0 +1,15 @@
1
+ type Variant = 'gray' | 'primary' | 'success' | 'warning' | 'error' | 'info';
2
+ interface Props {
3
+ label: string;
4
+ value: number | string;
5
+ icon?: string;
6
+ description?: string;
7
+ variant?: Variant;
8
+ trend?: 'up' | 'down' | 'neutral';
9
+ }
10
+ declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {
11
+ variant: Variant;
12
+ trend: "up" | "down" | "neutral";
13
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
14
+ declare const _default: typeof __VLS_export;
15
+ export default _default;
@@ -0,0 +1,99 @@
1
+ <template>
2
+ <div
3
+ class="rounded-lg border p-4 transition-all hover:shadow-md"
4
+ :class="[variantClasses, 'border-gray-200 dark:border-gray-800']"
5
+ >
6
+ <div class="flex items-start justify-between gap-3">
7
+ <div class="flex-1">
8
+ <p
9
+ class="text-xs font-semibold uppercase tracking-widest"
10
+ :class="labelColor"
11
+ >
12
+ {{ label }}
13
+ </p>
14
+ <p class="text-3xl font-bold mt-2 tracking-tight">
15
+ {{ formatValue(value) }}
16
+ </p>
17
+ <p
18
+ v-if="description"
19
+ class="text-xs mt-2"
20
+ :class="descriptionColor"
21
+ >
22
+ {{ description }}
23
+ </p>
24
+ </div>
25
+ <div
26
+ v-if="icon"
27
+ class="flex-shrink-0"
28
+ >
29
+ <UIcon
30
+ :name="icon"
31
+ class="w-6 h-6"
32
+ :class="iconColor"
33
+ />
34
+ </div>
35
+ </div>
36
+ </div>
37
+ </template>
38
+
39
+ <script setup>
40
+ import { computed } from "vue";
41
+ const props = defineProps({
42
+ label: { type: String, required: true },
43
+ value: { type: [Number, String], required: true },
44
+ icon: { type: String, required: false },
45
+ description: { type: String, required: false },
46
+ variant: { type: String, required: false, default: "gray" },
47
+ trend: { type: String, required: false, default: "neutral" }
48
+ });
49
+ const variantClasses = computed(() => {
50
+ const variants = {
51
+ gray: "bg-gray-50 dark:bg-gray-900/50",
52
+ primary: "bg-primary-50 dark:bg-primary-900/20",
53
+ success: "bg-emerald-50 dark:bg-emerald-900/20",
54
+ warning: "bg-amber-50 dark:bg-amber-900/20",
55
+ error: "bg-red-50 dark:bg-red-900/20",
56
+ info: "bg-blue-50 dark:bg-blue-900/20"
57
+ };
58
+ return variants[props.variant];
59
+ });
60
+ const labelColor = computed(() => {
61
+ const colors = {
62
+ gray: "text-gray-600 dark:text-gray-400",
63
+ primary: "text-primary-600 dark:text-primary-400",
64
+ success: "text-emerald-600 dark:text-emerald-400",
65
+ warning: "text-amber-600 dark:text-amber-400",
66
+ error: "text-red-600 dark:text-red-400",
67
+ info: "text-blue-600 dark:text-blue-400"
68
+ };
69
+ return colors[props.variant];
70
+ });
71
+ const descriptionColor = computed(() => {
72
+ const colors = {
73
+ gray: "text-gray-500 dark:text-gray-500",
74
+ primary: "text-primary-600 dark:text-primary-400",
75
+ success: "text-emerald-600 dark:text-emerald-400",
76
+ warning: "text-amber-600 dark:text-amber-400",
77
+ error: "text-red-600 dark:text-red-400",
78
+ info: "text-blue-600 dark:text-blue-400"
79
+ };
80
+ return colors[props.variant];
81
+ });
82
+ const iconColor = computed(() => {
83
+ const colors = {
84
+ gray: "text-gray-400 dark:text-gray-600",
85
+ primary: "text-primary-500",
86
+ success: "text-emerald-500",
87
+ warning: "text-amber-500",
88
+ error: "text-red-500",
89
+ info: "text-blue-500"
90
+ };
91
+ return colors[props.variant];
92
+ });
93
+ function formatValue(value) {
94
+ if (typeof value === "string") return value;
95
+ if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
96
+ if (value >= 1e3) return `${(value / 1e3).toFixed(1)}K`;
97
+ return value.toString();
98
+ }
99
+ </script>
@@ -0,0 +1,15 @@
1
+ type Variant = 'gray' | 'primary' | 'success' | 'warning' | 'error' | 'info';
2
+ interface Props {
3
+ label: string;
4
+ value: number | string;
5
+ icon?: string;
6
+ description?: string;
7
+ variant?: Variant;
8
+ trend?: 'up' | 'down' | 'neutral';
9
+ }
10
+ declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {
11
+ variant: Variant;
12
+ trend: "up" | "down" | "neutral";
13
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
14
+ declare const _default: typeof __VLS_export;
15
+ export default _default;
@@ -0,0 +1,11 @@
1
+ type ColorVariant = 'neutral' | 'primary' | 'success' | 'warning' | 'error' | 'info';
2
+ interface Props {
3
+ label: string;
4
+ count?: number;
5
+ color?: ColorVariant;
6
+ }
7
+ declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {
8
+ color: ColorVariant;
9
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
10
+ declare const _default: typeof __VLS_export;
11
+ export default _default;
@@ -0,0 +1,29 @@
1
+ <template>
2
+ <div
3
+ class="rounded-lg border px-3 py-2 inline-flex flex-col gap-1 text-center transition-colors hover:bg-opacity-75"
4
+ :class="[variantClasses, 'border-current']"
5
+ >
6
+ <span class="text-xs font-semibold uppercase tracking-wider opacity-75">{{ label }}</span>
7
+ <span class="text-lg font-bold tabular-nums">{{ count ?? 0 }}</span>
8
+ </div>
9
+ </template>
10
+
11
+ <script setup>
12
+ import { computed } from "vue";
13
+ const props = defineProps({
14
+ label: { type: String, required: true },
15
+ count: { type: Number, required: false },
16
+ color: { type: String, required: false, default: "neutral" }
17
+ });
18
+ const variantClasses = computed(() => {
19
+ const variants = {
20
+ neutral: "border-gray-400 dark:border-gray-600 bg-gray-50 dark:bg-gray-900/50 text-gray-700 dark:text-gray-300",
21
+ primary: "border-primary-400 dark:border-primary-600 bg-primary-50 dark:bg-primary-900/20 text-primary-700 dark:text-primary-300",
22
+ success: "border-emerald-400 dark:border-emerald-600 bg-emerald-50 dark:bg-emerald-900/20 text-emerald-700 dark:text-emerald-300",
23
+ warning: "border-amber-400 dark:border-amber-600 bg-amber-50 dark:bg-amber-900/20 text-amber-700 dark:text-amber-300",
24
+ error: "border-red-400 dark:border-red-600 bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-300",
25
+ info: "border-blue-400 dark:border-blue-600 bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300"
26
+ };
27
+ return variants[props.color];
28
+ });
29
+ </script>
@@ -0,0 +1,11 @@
1
+ type ColorVariant = 'neutral' | 'primary' | 'success' | 'warning' | 'error' | 'info';
2
+ interface Props {
3
+ label: string;
4
+ count?: number;
5
+ color?: ColorVariant;
6
+ }
7
+ declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {
8
+ color: ColorVariant;
9
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
10
+ declare const _default: typeof __VLS_export;
11
+ export default _default;