cynx-lib 1.3.80 → 1.3.82

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 (41) hide show
  1. package/gen.js +77 -77
  2. package/package.json +1 -1
  3. package/ui/components/Drawer.jsx +208 -208
  4. package/ui/components/Layout.jsx +138 -138
  5. package/ui/components/LoginPage.jsx +405 -405
  6. package/ui/components/Pagination.jsx +145 -145
  7. package/ui/components/PrivacyBlur.jsx +603 -603
  8. package/ui/components/Sidebar.jsx +820 -820
  9. package/utils/index.js +20 -20
  10. package/utils/utils/duration.js +139 -139
  11. package/utils/utils/oidc.js +214 -214
  12. package/utils/utils/realtime.js +1634 -1634
  13. package/utils/utils/session.js +58 -58
  14. package/utils/utils/storage.js +393 -393
  15. package/utils/utils/storage_manager.cjs +84 -7
  16. package/utils/utils/storage_manager.js +95 -8
  17. package/utils/utils/ui.js +111 -111
  18. package/vue/components/Badge.vue +79 -79
  19. package/vue/components/Button.vue +209 -209
  20. package/vue/components/Checkbox.vue +103 -103
  21. package/vue/components/CircularProgress.vue +383 -383
  22. package/vue/components/Divider.vue +94 -94
  23. package/vue/components/Drawer.vue +243 -243
  24. package/vue/components/InfoBadge.vue +223 -223
  25. package/vue/components/Input.vue +341 -341
  26. package/vue/components/LinearProgress.vue +268 -268
  27. package/vue/components/Modal.vue +226 -226
  28. package/vue/components/Pagination.vue +199 -199
  29. package/vue/components/Select.vue +713 -713
  30. package/vue/components/Sidebar.vue +955 -955
  31. package/vue/components/Skeleton.vue +91 -91
  32. package/vue/components/Spinner.vue +263 -263
  33. package/vue/components/StatusTag.vue +89 -89
  34. package/vue/components/TabBar.vue +176 -176
  35. package/vue/components/Table.vue +388 -388
  36. package/vue/components/TimePicker.vue +399 -399
  37. package/vue/components/Toggle.vue +115 -115
  38. package/vue/components/XTable.vue +119 -119
  39. package/vue/composables/useAutoSkeletonRows.ts +71 -71
  40. package/vue/index.d.ts +67 -67
  41. package/vue/index.js +86 -86
@@ -1,79 +1,79 @@
1
- <script setup lang="ts">
2
- import { computed } from "vue";
3
-
4
- const VARIANTS: Record<string, any> = {
5
- ok: { bg: "var(--success-bg, rgba(39,174,96,.1))", color: "var(--success, #16a34a)", border: "var(--sd, rgba(39,174,96,.2))", dot: "var(--success, #16a34a)" },
6
- success: { bg: "var(--success-bg, rgba(39,174,96,.1))", color: "var(--success, #16a34a)", border: "var(--sd, rgba(39,174,96,.2))", dot: "var(--success, #16a34a)" },
7
- err: { bg: "var(--danger-bg, rgba(246,71,71,.1))", color: "var(--danger, #ef4444)", border: "var(--dd, rgba(246,71,71,.2))", dot: "var(--danger, #ef4444)" },
8
- danger: { bg: "var(--danger-bg, rgba(246,71,71,.1))", color: "var(--danger, #ef4444)", border: "var(--dd, rgba(246,71,71,.2))", dot: "var(--danger, #ef4444)" },
9
- error: { bg: "var(--danger-bg, rgba(246,71,71,.1))", color: "var(--danger, #ef4444)", border: "var(--dd, rgba(246,71,71,.2))", dot: "var(--danger, #ef4444)" },
10
- warn: { bg: "var(--accent-bg, rgba(249,158,44,.1))", color: "var(--accent, #f59e0b)", border: "var(--accent-border, rgba(249,158,44,.25))", dot: "var(--accent, #f59e0b)" },
11
- warning: { bg: "var(--accent-bg, rgba(249,158,44,.1))", color: "var(--accent, #f59e0b)", border: "var(--accent-border, rgba(249,158,44,.25))", dot: "var(--accent, #f59e0b)" },
12
- blue: { bg: "var(--blue-bg, rgba(39,130,228,.1))", color: "var(--blue, #2563eb)", border: "var(--infd, rgba(39,130,228,.2))", dot: "var(--blue, #2563eb)" },
13
- info: { bg: "var(--blue-bg, rgba(39,130,228,.1))", color: "var(--blue, #2563eb)", border: "var(--infd, rgba(39,130,228,.2))", dot: "var(--blue, #2563eb)" },
14
- teal: { bg: "rgba(18,103,93,.1)", color: "var(--accent, #12675D)", border: "rgba(18,103,93,.25)", dot: "var(--accent, #12675D)" },
15
- primary: { bg: "rgba(18,103,93,.1)", color: "var(--accent, #12675D)", border: "rgba(18,103,93,.25)", dot: "var(--accent, #12675D)" },
16
- dim: { bg: "var(--grey-1, #f5f5f8)", color: "var(--grey-4, #64748b)", border: "var(--border, #e2e8f0)", dot: "var(--grey-4, #64748b)" },
17
- neutral: { bg: "var(--grey-1, #f5f5f8)", color: "var(--grey-4, #64748b)", border: "var(--border, #e2e8f0)", dot: "var(--grey-4, #64748b)" },
18
- secondary: { bg: "var(--grey-1, #f5f5f8)", color: "var(--grey-4, #64748b)", border: "var(--border, #e2e8f0)", dot: "var(--grey-4, #64748b)" },
19
- ghost: { bg: "transparent", color: "var(--t2, #64748b)", border: "var(--bd, #e2e8f0)", dot: "var(--t3, #94a3b8)" },
20
- };
21
-
22
- const SIZE_MAP: Record<string, any> = {
23
- xs: { padding: "1.5px 6px", plainPadding: "1px 0", fontSize: "9.5px", dotSize: 5 },
24
- sm: { padding: "2px 8px", plainPadding: "2px 0", fontSize: "11px", dotSize: 6 },
25
- md: { padding: "3px 9px", plainPadding: "2px 0", fontSize: "12px", dotSize: 6.5 },
26
- lg: { padding: "4px 12px", plainPadding: "3px 0", fontSize: "13px", dotSize: 8 },
27
- };
28
-
29
- const props = withDefaults(
30
- defineProps<{
31
- variant?: string;
32
- size?: "xs" | "sm" | "md" | "lg";
33
- dot?: boolean;
34
- pill?: boolean;
35
- plain?: boolean;
36
- minimal?: boolean;
37
- }>(),
38
- {
39
- variant: "dim",
40
- size: "sm",
41
- dot: false,
42
- pill: true,
43
- plain: false,
44
- minimal: false,
45
- }
46
- );
47
-
48
- const v = computed(() => VARIANTS[props.variant] || VARIANTS.dim);
49
- const s = computed(() => SIZE_MAP[props.size] || SIZE_MAP.sm);
50
- const isPlain = computed(() => props.plain || props.minimal);
51
- </script>
52
-
53
- <template>
54
- <span
55
- :class="[
56
- 'inline-flex items-center gap-1.5 select-none',
57
- isPlain ? 'font-semibold' : 'font-bold leading-none',
58
- isPlain ? '' : (pill ? 'rounded-full' : 'rounded-md')
59
- ]"
60
- :style="{
61
- background: isPlain ? 'transparent' : v.bg,
62
- color: v.color,
63
- border: isPlain ? 'none' : `1px solid ${v.border}`,
64
- padding: isPlain ? s.plainPadding : s.padding,
65
- fontSize: s.fontSize,
66
- }"
67
- >
68
- <span
69
- v-if="dot"
70
- class="rounded-full flex-shrink-0"
71
- :style="{
72
- width: `${s.dotSize}px`,
73
- height: `${s.dotSize}px`,
74
- background: v.dot,
75
- }"
76
- />
77
- <slot />
78
- </span>
79
- </template>
1
+ <script setup lang="ts">
2
+ import { computed } from "vue";
3
+
4
+ const VARIANTS: Record<string, any> = {
5
+ ok: { bg: "var(--success-bg, rgba(39,174,96,.1))", color: "var(--success, #16a34a)", border: "var(--sd, rgba(39,174,96,.2))", dot: "var(--success, #16a34a)" },
6
+ success: { bg: "var(--success-bg, rgba(39,174,96,.1))", color: "var(--success, #16a34a)", border: "var(--sd, rgba(39,174,96,.2))", dot: "var(--success, #16a34a)" },
7
+ err: { bg: "var(--danger-bg, rgba(246,71,71,.1))", color: "var(--danger, #ef4444)", border: "var(--dd, rgba(246,71,71,.2))", dot: "var(--danger, #ef4444)" },
8
+ danger: { bg: "var(--danger-bg, rgba(246,71,71,.1))", color: "var(--danger, #ef4444)", border: "var(--dd, rgba(246,71,71,.2))", dot: "var(--danger, #ef4444)" },
9
+ error: { bg: "var(--danger-bg, rgba(246,71,71,.1))", color: "var(--danger, #ef4444)", border: "var(--dd, rgba(246,71,71,.2))", dot: "var(--danger, #ef4444)" },
10
+ warn: { bg: "var(--accent-bg, rgba(249,158,44,.1))", color: "var(--accent, #f59e0b)", border: "var(--accent-border, rgba(249,158,44,.25))", dot: "var(--accent, #f59e0b)" },
11
+ warning: { bg: "var(--accent-bg, rgba(249,158,44,.1))", color: "var(--accent, #f59e0b)", border: "var(--accent-border, rgba(249,158,44,.25))", dot: "var(--accent, #f59e0b)" },
12
+ blue: { bg: "var(--blue-bg, rgba(39,130,228,.1))", color: "var(--blue, #2563eb)", border: "var(--infd, rgba(39,130,228,.2))", dot: "var(--blue, #2563eb)" },
13
+ info: { bg: "var(--blue-bg, rgba(39,130,228,.1))", color: "var(--blue, #2563eb)", border: "var(--infd, rgba(39,130,228,.2))", dot: "var(--blue, #2563eb)" },
14
+ teal: { bg: "rgba(18,103,93,.1)", color: "var(--accent, #12675D)", border: "rgba(18,103,93,.25)", dot: "var(--accent, #12675D)" },
15
+ primary: { bg: "rgba(18,103,93,.1)", color: "var(--accent, #12675D)", border: "rgba(18,103,93,.25)", dot: "var(--accent, #12675D)" },
16
+ dim: { bg: "var(--grey-1, #f5f5f8)", color: "var(--grey-4, #64748b)", border: "var(--border, #e2e8f0)", dot: "var(--grey-4, #64748b)" },
17
+ neutral: { bg: "var(--grey-1, #f5f5f8)", color: "var(--grey-4, #64748b)", border: "var(--border, #e2e8f0)", dot: "var(--grey-4, #64748b)" },
18
+ secondary: { bg: "var(--grey-1, #f5f5f8)", color: "var(--grey-4, #64748b)", border: "var(--border, #e2e8f0)", dot: "var(--grey-4, #64748b)" },
19
+ ghost: { bg: "transparent", color: "var(--t2, #64748b)", border: "var(--bd, #e2e8f0)", dot: "var(--t3, #94a3b8)" },
20
+ };
21
+
22
+ const SIZE_MAP: Record<string, any> = {
23
+ xs: { padding: "1.5px 6px", plainPadding: "1px 0", fontSize: "9.5px", dotSize: 5 },
24
+ sm: { padding: "2px 8px", plainPadding: "2px 0", fontSize: "11px", dotSize: 6 },
25
+ md: { padding: "3px 9px", plainPadding: "2px 0", fontSize: "12px", dotSize: 6.5 },
26
+ lg: { padding: "4px 12px", plainPadding: "3px 0", fontSize: "13px", dotSize: 8 },
27
+ };
28
+
29
+ const props = withDefaults(
30
+ defineProps<{
31
+ variant?: string;
32
+ size?: "xs" | "sm" | "md" | "lg";
33
+ dot?: boolean;
34
+ pill?: boolean;
35
+ plain?: boolean;
36
+ minimal?: boolean;
37
+ }>(),
38
+ {
39
+ variant: "dim",
40
+ size: "sm",
41
+ dot: false,
42
+ pill: true,
43
+ plain: false,
44
+ minimal: false,
45
+ }
46
+ );
47
+
48
+ const v = computed(() => VARIANTS[props.variant] || VARIANTS.dim);
49
+ const s = computed(() => SIZE_MAP[props.size] || SIZE_MAP.sm);
50
+ const isPlain = computed(() => props.plain || props.minimal);
51
+ </script>
52
+
53
+ <template>
54
+ <span
55
+ :class="[
56
+ 'inline-flex items-center gap-1.5 select-none',
57
+ isPlain ? 'font-semibold' : 'font-bold leading-none',
58
+ isPlain ? '' : (pill ? 'rounded-full' : 'rounded-md')
59
+ ]"
60
+ :style="{
61
+ background: isPlain ? 'transparent' : v.bg,
62
+ color: v.color,
63
+ border: isPlain ? 'none' : `1px solid ${v.border}`,
64
+ padding: isPlain ? s.plainPadding : s.padding,
65
+ fontSize: s.fontSize,
66
+ }"
67
+ >
68
+ <span
69
+ v-if="dot"
70
+ class="rounded-full flex-shrink-0"
71
+ :style="{
72
+ width: `${s.dotSize}px`,
73
+ height: `${s.dotSize}px`,
74
+ background: v.dot,
75
+ }"
76
+ />
77
+ <slot />
78
+ </span>
79
+ </template>
@@ -1,209 +1,209 @@
1
- <script setup lang="ts">
2
- import { computed, useSlots, type VNode } from "vue";
3
- import Spinner from "./Spinner.vue";
4
-
5
- const VARIANTS: Record<string, any> = {
6
- default: {
7
- background: "var(--surface, #fff)",
8
- color: "var(--def-btn-text, #0f111c)",
9
- border: "1px solid var(--bd, #d7d8e0)",
10
- hoverBg: "var(--grey-1, #f5f5f8)",
11
- hoverBorder: "var(--grey-2, #ebebee)",
12
- },
13
- primary: {
14
- background: "var(--accent, #12675D)",
15
- color: "#fff",
16
- border: "1px solid transparent",
17
- hoverBg: "var(--accent-hover, #0e524a)",
18
- hoverShadow: "0 2px 8px rgba(18, 103, 93, .35)",
19
- },
20
- secondary: {
21
- background: "var(--blue-bg, rgba(39,130,228,.1))",
22
- color: "var(--blue, #2782e4)",
23
- border: "1px solid var(--infd, rgba(39,130,228,.2))",
24
- hoverBg: "var(--infd, rgba(39,130,228,.18))",
25
- hoverShadow: "0 2px 8px rgba(39,130,228,.25)",
26
- },
27
- danger: {
28
- background: "var(--danger-bg, rgba(246,71,71,.1))",
29
- color: "var(--danger, #f64747)",
30
- border: "1px solid var(--dd, rgba(246,71,71,.2))",
31
- hoverBg: "var(--dd, rgba(246,71,71,.18))",
32
- hoverShadow: "0 2px 8px rgba(246,71,71,.25)",
33
- },
34
- "danger-solid": {
35
- background: "var(--danger, #EF4444)",
36
- color: "#fff",
37
- border: "1px solid transparent",
38
- hoverBg: "#dc2626",
39
- hoverShadow: "0 2px 8px rgba(239, 68, 68, .35)",
40
- },
41
- "danger-fill": {
42
- background: "var(--danger, #EF4444)",
43
- color: "#fff",
44
- border: "1px solid transparent",
45
- hoverBg: "#dc2626",
46
- hoverShadow: "0 2px 8px rgba(239, 68, 68, .35)",
47
- },
48
- success: {
49
- background: "var(--success-bg, rgba(39,174,96,.1))",
50
- color: "var(--success, #27ae60)",
51
- border: "1px solid var(--sd, rgba(39,174,96,.2))",
52
- hoverBg: "var(--sd, rgba(39,174,96,.18))",
53
- hoverShadow: "0 2px 8px rgba(39,174,96,.25)",
54
- },
55
- warning: {
56
- background: "var(--accent-bg, rgba(249,158,44,.1))",
57
- color: "var(--accent, #f99e2c)",
58
- border: "1px solid var(--accent-border, rgba(249,158,44,.25))",
59
- hoverBg: "var(--pd, rgba(249,158,44,.18))",
60
- hoverShadow: "0 2px 8px rgba(249,158,44,.25)",
61
- },
62
- ghost: {
63
- background: "var(--surface, #ffffff)",
64
- color: "var(--t2, #797b8d)",
65
- border: "1px solid var(--bd, #d7d8e0)",
66
- hoverColor: "var(--t1, #1a1a2e)",
67
- hoverBg: "var(--grey-1, #f5f5f8)",
68
- hoverBorder: "var(--grey-2, #ebebee)",
69
- },
70
- plain: {
71
- background: "transparent",
72
- color: "var(--t2, #797b8d)",
73
- border: "1px solid transparent",
74
- hoverColor: "var(--t1, #1a1a2e)",
75
- hoverBg: "var(--grey-1, #f5f5f8)",
76
- hoverBorder: "transparent",
77
- boxShadow: "none",
78
- },
79
- flat: {
80
- background: "transparent",
81
- color: "var(--t2, #797b8d)",
82
- border: "1px solid transparent",
83
- hoverColor: "var(--t1, #1a1a2e)",
84
- hoverBg: "var(--grey-1, #f5f5f8)",
85
- hoverBorder: "transparent",
86
- boxShadow: "none",
87
- },
88
- icon: {
89
- background: "transparent",
90
- color: "var(--t2, #797b8d)",
91
- border: "1px solid transparent",
92
- hoverColor: "var(--t1, #1a1a2e)",
93
- hoverBg: "var(--grey-1, #f5f5f8)",
94
- hoverBorder: "transparent",
95
- boxShadow: "none",
96
- },
97
- outline: {
98
- background: "transparent",
99
- color: "var(--accent, #12675D)",
100
- border: "1px solid var(--accent, #12675D)",
101
- hoverBg: "var(--accent-bg, rgba(18, 103, 93, .1))",
102
- },
103
- link: {
104
- background: "transparent",
105
- color: "var(--accent, #12675D)",
106
- border: "1px solid transparent",
107
- paddingLeft: "4px",
108
- paddingRight: "4px",
109
- boxShadow: "none",
110
- },
111
- "no-line": {
112
- background: "none",
113
- border: "none",
114
- boxShadow: "none",
115
- color: "var(--t3, #8a96b0)",
116
- paddingLeft: "14px",
117
- paddingRight: "14px",
118
- borderRadius: "var(--rmd, 8px)",
119
- },
120
- };
121
-
122
- const SIZES: Record<string, any> = {
123
- xs: { padding: "0 6px", fontSize: 11, height: 24 },
124
- sm: { padding: "0 8px", fontSize: 11.5, height: 28 },
125
- md: { padding: "0 14px", fontSize: 13, height: 32 },
126
- lg: { padding: "0 18px", fontSize: 14, height: 36 },
127
- };
128
-
129
- const props = withDefaults(
130
- defineProps<{
131
- variant?: string;
132
- size?: "xs" | "sm" | "md" | "lg";
133
- disabled?: boolean;
134
- loading?: boolean;
135
- type?: "button" | "submit" | "reset";
136
- iconOnly?: boolean;
137
- }>(),
138
- {
139
- variant: "default",
140
- size: "md",
141
- disabled: false,
142
- loading: false,
143
- type: "button",
144
- iconOnly: false,
145
- }
146
- );
147
-
148
- const emit = defineEmits<{
149
- (e: "click", event: MouseEvent): void;
150
- }>();
151
-
152
- const slots = useSlots();
153
- const v = computed(() => VARIANTS[props.variant] || VARIANTS.default);
154
- const s = computed(() => SIZES[props.size] || SIZES.md);
155
-
156
- const hasDefaultText = computed(() => {
157
- if (!slots.default) return false;
158
- const nodes = slots.default();
159
- if (!nodes || nodes.length === 0) return false;
160
- return nodes.some((node: VNode) => {
161
- if (node.type === Symbol.for("v-cmt")) return false;
162
- if (typeof node.children === "string") return node.children.trim().length > 0;
163
- if (Array.isArray(node.children)) return node.children.length > 0;
164
- return Boolean(node.type);
165
- });
166
- });
167
-
168
- const isIconOnly = computed(() => {
169
- if (props.iconOnly || props.variant === "icon") return true;
170
- const hasIconSlot = Boolean(slots.icon) || Boolean(slots.iconRight);
171
- return hasIconSlot && !hasDefaultText.value;
172
- });
173
- </script>
174
-
175
- <template>
176
- <button
177
- :type="type"
178
- :disabled="disabled || loading"
179
- :class="[`btn btn-${variant}`, 'inline-flex items-center justify-center gap-1.5 font-medium transition select-none flex-shrink-0']"
180
- :style="{
181
- borderRadius: 'var(--rads, 8px)',
182
- fontFamily: 'var(--f, var(--sans, inherit))',
183
- cursor: disabled ? 'not-allowed' : 'pointer',
184
- whiteSpace: 'nowrap',
185
- letterSpacing: '-.01em',
186
- outline: 'none',
187
- boxShadow: v.boxShadow !== undefined ? v.boxShadow : 'var(--shadow, 0 1px 3px rgba(20,22,41,.1))',
188
- opacity: disabled ? 0.45 : 1,
189
- pointerEvents: disabled ? 'none' : undefined,
190
- background: v.background,
191
- color: v.color,
192
- border: v.border,
193
- padding: isIconOnly ? '0' : s.padding,
194
- fontSize: `${s.fontSize}px`,
195
- height: `${s.height}px`,
196
- width: isIconOnly ? `${s.height}px` : undefined,
197
- minWidth: isIconOnly ? `${s.height}px` : undefined,
198
- maxWidth: isIconOnly ? `${s.height}px` : undefined,
199
- ...(v.paddingLeft !== undefined && { paddingLeft: v.paddingLeft }),
200
- ...(v.paddingRight !== undefined && { paddingRight: v.paddingRight }),
201
- }"
202
- @click="emit('click', $event)"
203
- >
204
- <Spinner v-if="loading" :size="14" inline color="currentColor" />
205
- <slot v-if="!loading" name="icon" />
206
- <slot v-if="!loading" />
207
- <slot v-if="!loading" name="iconRight" />
208
- </button>
209
- </template>
1
+ <script setup lang="ts">
2
+ import { computed, useSlots, type VNode } from "vue";
3
+ import Spinner from "./Spinner.vue";
4
+
5
+ const VARIANTS: Record<string, any> = {
6
+ default: {
7
+ background: "var(--surface, #fff)",
8
+ color: "var(--def-btn-text, #0f111c)",
9
+ border: "1px solid var(--bd, #d7d8e0)",
10
+ hoverBg: "var(--grey-1, #f5f5f8)",
11
+ hoverBorder: "var(--grey-2, #ebebee)",
12
+ },
13
+ primary: {
14
+ background: "var(--accent, #12675D)",
15
+ color: "#fff",
16
+ border: "1px solid transparent",
17
+ hoverBg: "var(--accent-hover, #0e524a)",
18
+ hoverShadow: "0 2px 8px rgba(18, 103, 93, .35)",
19
+ },
20
+ secondary: {
21
+ background: "var(--blue-bg, rgba(39,130,228,.1))",
22
+ color: "var(--blue, #2782e4)",
23
+ border: "1px solid var(--infd, rgba(39,130,228,.2))",
24
+ hoverBg: "var(--infd, rgba(39,130,228,.18))",
25
+ hoverShadow: "0 2px 8px rgba(39,130,228,.25)",
26
+ },
27
+ danger: {
28
+ background: "var(--danger-bg, rgba(246,71,71,.1))",
29
+ color: "var(--danger, #f64747)",
30
+ border: "1px solid var(--dd, rgba(246,71,71,.2))",
31
+ hoverBg: "var(--dd, rgba(246,71,71,.18))",
32
+ hoverShadow: "0 2px 8px rgba(246,71,71,.25)",
33
+ },
34
+ "danger-solid": {
35
+ background: "var(--danger, #EF4444)",
36
+ color: "#fff",
37
+ border: "1px solid transparent",
38
+ hoverBg: "#dc2626",
39
+ hoverShadow: "0 2px 8px rgba(239, 68, 68, .35)",
40
+ },
41
+ "danger-fill": {
42
+ background: "var(--danger, #EF4444)",
43
+ color: "#fff",
44
+ border: "1px solid transparent",
45
+ hoverBg: "#dc2626",
46
+ hoverShadow: "0 2px 8px rgba(239, 68, 68, .35)",
47
+ },
48
+ success: {
49
+ background: "var(--success-bg, rgba(39,174,96,.1))",
50
+ color: "var(--success, #27ae60)",
51
+ border: "1px solid var(--sd, rgba(39,174,96,.2))",
52
+ hoverBg: "var(--sd, rgba(39,174,96,.18))",
53
+ hoverShadow: "0 2px 8px rgba(39,174,96,.25)",
54
+ },
55
+ warning: {
56
+ background: "var(--accent-bg, rgba(249,158,44,.1))",
57
+ color: "var(--accent, #f99e2c)",
58
+ border: "1px solid var(--accent-border, rgba(249,158,44,.25))",
59
+ hoverBg: "var(--pd, rgba(249,158,44,.18))",
60
+ hoverShadow: "0 2px 8px rgba(249,158,44,.25)",
61
+ },
62
+ ghost: {
63
+ background: "var(--surface, #ffffff)",
64
+ color: "var(--t2, #797b8d)",
65
+ border: "1px solid var(--bd, #d7d8e0)",
66
+ hoverColor: "var(--t1, #1a1a2e)",
67
+ hoverBg: "var(--grey-1, #f5f5f8)",
68
+ hoverBorder: "var(--grey-2, #ebebee)",
69
+ },
70
+ plain: {
71
+ background: "transparent",
72
+ color: "var(--t2, #797b8d)",
73
+ border: "1px solid transparent",
74
+ hoverColor: "var(--t1, #1a1a2e)",
75
+ hoverBg: "var(--grey-1, #f5f5f8)",
76
+ hoverBorder: "transparent",
77
+ boxShadow: "none",
78
+ },
79
+ flat: {
80
+ background: "transparent",
81
+ color: "var(--t2, #797b8d)",
82
+ border: "1px solid transparent",
83
+ hoverColor: "var(--t1, #1a1a2e)",
84
+ hoverBg: "var(--grey-1, #f5f5f8)",
85
+ hoverBorder: "transparent",
86
+ boxShadow: "none",
87
+ },
88
+ icon: {
89
+ background: "transparent",
90
+ color: "var(--t2, #797b8d)",
91
+ border: "1px solid transparent",
92
+ hoverColor: "var(--t1, #1a1a2e)",
93
+ hoverBg: "var(--grey-1, #f5f5f8)",
94
+ hoverBorder: "transparent",
95
+ boxShadow: "none",
96
+ },
97
+ outline: {
98
+ background: "transparent",
99
+ color: "var(--accent, #12675D)",
100
+ border: "1px solid var(--accent, #12675D)",
101
+ hoverBg: "var(--accent-bg, rgba(18, 103, 93, .1))",
102
+ },
103
+ link: {
104
+ background: "transparent",
105
+ color: "var(--accent, #12675D)",
106
+ border: "1px solid transparent",
107
+ paddingLeft: "4px",
108
+ paddingRight: "4px",
109
+ boxShadow: "none",
110
+ },
111
+ "no-line": {
112
+ background: "none",
113
+ border: "none",
114
+ boxShadow: "none",
115
+ color: "var(--t3, #8a96b0)",
116
+ paddingLeft: "14px",
117
+ paddingRight: "14px",
118
+ borderRadius: "var(--rmd, 8px)",
119
+ },
120
+ };
121
+
122
+ const SIZES: Record<string, any> = {
123
+ xs: { padding: "0 6px", fontSize: 11, height: 24 },
124
+ sm: { padding: "0 8px", fontSize: 11.5, height: 28 },
125
+ md: { padding: "0 14px", fontSize: 13, height: 32 },
126
+ lg: { padding: "0 18px", fontSize: 14, height: 36 },
127
+ };
128
+
129
+ const props = withDefaults(
130
+ defineProps<{
131
+ variant?: string;
132
+ size?: "xs" | "sm" | "md" | "lg";
133
+ disabled?: boolean;
134
+ loading?: boolean;
135
+ type?: "button" | "submit" | "reset";
136
+ iconOnly?: boolean;
137
+ }>(),
138
+ {
139
+ variant: "default",
140
+ size: "md",
141
+ disabled: false,
142
+ loading: false,
143
+ type: "button",
144
+ iconOnly: false,
145
+ }
146
+ );
147
+
148
+ const emit = defineEmits<{
149
+ (e: "click", event: MouseEvent): void;
150
+ }>();
151
+
152
+ const slots = useSlots();
153
+ const v = computed(() => VARIANTS[props.variant] || VARIANTS.default);
154
+ const s = computed(() => SIZES[props.size] || SIZES.md);
155
+
156
+ const hasDefaultText = computed(() => {
157
+ if (!slots.default) return false;
158
+ const nodes = slots.default();
159
+ if (!nodes || nodes.length === 0) return false;
160
+ return nodes.some((node: VNode) => {
161
+ if (node.type === Symbol.for("v-cmt")) return false;
162
+ if (typeof node.children === "string") return node.children.trim().length > 0;
163
+ if (Array.isArray(node.children)) return node.children.length > 0;
164
+ return Boolean(node.type);
165
+ });
166
+ });
167
+
168
+ const isIconOnly = computed(() => {
169
+ if (props.iconOnly || props.variant === "icon") return true;
170
+ const hasIconSlot = Boolean(slots.icon) || Boolean(slots.iconRight);
171
+ return hasIconSlot && !hasDefaultText.value;
172
+ });
173
+ </script>
174
+
175
+ <template>
176
+ <button
177
+ :type="type"
178
+ :disabled="disabled || loading"
179
+ :class="[`btn btn-${variant}`, 'inline-flex items-center justify-center gap-1.5 font-medium transition select-none flex-shrink-0']"
180
+ :style="{
181
+ borderRadius: 'var(--rads, 8px)',
182
+ fontFamily: 'var(--f, var(--sans, inherit))',
183
+ cursor: disabled ? 'not-allowed' : 'pointer',
184
+ whiteSpace: 'nowrap',
185
+ letterSpacing: '-.01em',
186
+ outline: 'none',
187
+ boxShadow: v.boxShadow !== undefined ? v.boxShadow : 'var(--shadow, 0 1px 3px rgba(20,22,41,.1))',
188
+ opacity: disabled ? 0.45 : 1,
189
+ pointerEvents: disabled ? 'none' : undefined,
190
+ background: v.background,
191
+ color: v.color,
192
+ border: v.border,
193
+ padding: isIconOnly ? '0' : s.padding,
194
+ fontSize: `${s.fontSize}px`,
195
+ height: `${s.height}px`,
196
+ width: isIconOnly ? `${s.height}px` : undefined,
197
+ minWidth: isIconOnly ? `${s.height}px` : undefined,
198
+ maxWidth: isIconOnly ? `${s.height}px` : undefined,
199
+ ...(v.paddingLeft !== undefined && { paddingLeft: v.paddingLeft }),
200
+ ...(v.paddingRight !== undefined && { paddingRight: v.paddingRight }),
201
+ }"
202
+ @click="emit('click', $event)"
203
+ >
204
+ <Spinner v-if="loading" :size="14" inline color="currentColor" />
205
+ <slot v-if="!loading" name="icon" />
206
+ <slot v-if="!loading" />
207
+ <slot v-if="!loading" name="iconRight" />
208
+ </button>
209
+ </template>