cynx-lib 1.3.51 → 1.3.53

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/ui/index.cjs CHANGED
@@ -55,6 +55,7 @@ __export(index_exports, {
55
55
  Pagination: () => Pagination,
56
56
  PulseDot: () => PulseDot,
57
57
  Select: () => Select,
58
+ Sidebar: () => Sidebar,
58
59
  Skeleton: () => Skeleton,
59
60
  SkeletonCard: () => SkeletonCard,
60
61
  SkeletonRows: () => SkeletonRows,
@@ -126,8 +127,10 @@ var import_FilePicker = __toESM(require("./components/FilePicker.jsx"), 1);
126
127
  var import_Divider = __toESM(require("./components/Divider.jsx"), 1);
127
128
  var import_InfoBadge = __toESM(require("./components/InfoBadge.jsx"), 1);
128
129
  var import_Layout = __toESM(require("./components/Layout.jsx"), 1);
130
+ var import_Sidebar = __toESM(require("./components/Sidebar.jsx"), 1);
129
131
  var import_LoginPage = __toESM(require("./components/LoginPage.jsx"), 1);
130
132
  const Layout = import_Layout.default;
133
+ const Sidebar = import_Sidebar.default;
131
134
  const LoginPage = import_LoginPage.default;
132
135
  const Button = import_Button.default;
133
136
  const FilePicker = import_FilePicker.default;
package/ui/index.js CHANGED
@@ -38,9 +38,11 @@ import { default as _FilePicker } from './components/FilePicker.jsx';
38
38
  import { default as _Divider } from './components/Divider.jsx';
39
39
  import { default as _InfoBadge } from './components/InfoBadge.jsx';
40
40
  import { default as _Layout } from './components/Layout.jsx';
41
+ import { default as _Sidebar } from './components/Sidebar.jsx';
41
42
  import { default as _LoginPage } from './components/LoginPage.jsx';
42
43
 
43
44
  export const Layout = _Layout;
45
+ export const Sidebar = _Sidebar;
44
46
  export const LoginPage = _LoginPage;
45
47
  export const Button = _Button;
46
48
  export const FilePicker = _FilePicker;
@@ -0,0 +1,64 @@
1
+ <script setup lang="ts">
2
+ import { computed } from "vue";
3
+
4
+ const VARIANTS: Record<string, any> = {
5
+ ok: { bg: "rgba(39,174,96,.1)", color: "#16a34a", border: "rgba(39,174,96,.2)", dot: "#16a34a" },
6
+ err: { bg: "rgba(246,71,71,.1)", color: "#ef4444", border: "rgba(246,71,71,.2)", dot: "#ef4444" },
7
+ warn: { bg: "rgba(249,158,44,.1)", color: "#f59e0b", border: "rgba(249,158,44,.25)", dot: "#f59e0b" },
8
+ blue: { bg: "rgba(39,130,228,.1)", color: "#2563eb", border: "rgba(39,130,228,.2)", dot: "#2563eb" },
9
+ teal: { bg: "rgba(18,103,93,.1)", color: "#12675D", border: "rgba(18,103,93,.25)", dot: "#12675D" },
10
+ dim: { bg: "#f1f5f9", color: "#64748b", border: "#e2e8f0", dot: "#64748b" },
11
+ };
12
+
13
+ const SIZE_MAP: Record<string, any> = {
14
+ xs: { padding: "1.5px 6px", fontSize: "9.5px", dotSize: 5 },
15
+ sm: { padding: "2px 8px", fontSize: "11px", dotSize: 6 },
16
+ md: { padding: "3px 9px", fontSize: "12px", dotSize: 6.5 },
17
+ lg: { padding: "4px 12px", fontSize: "13px", dotSize: 8 },
18
+ };
19
+
20
+ const props = withDefaults(
21
+ defineProps<{
22
+ variant?: string;
23
+ size?: "xs" | "sm" | "md" | "lg";
24
+ dot?: boolean;
25
+ pill?: boolean;
26
+ }>(),
27
+ {
28
+ variant: "dim",
29
+ size: "sm",
30
+ dot: false,
31
+ pill: true,
32
+ }
33
+ );
34
+
35
+ const v = computed(() => VARIANTS[props.variant] || VARIANTS.dim);
36
+ const s = computed(() => SIZE_MAP[props.size] || SIZE_MAP.sm);
37
+ </script>
38
+
39
+ <template>
40
+ <span
41
+ :class="[
42
+ 'inline-flex items-center gap-1.5 font-bold leading-none select-none',
43
+ pill ? 'rounded-full' : 'rounded-md'
44
+ ]"
45
+ :style="{
46
+ background: v.bg,
47
+ color: v.color,
48
+ border: `1px solid ${v.border}`,
49
+ padding: s.padding,
50
+ fontSize: s.fontSize,
51
+ }"
52
+ >
53
+ <span
54
+ v-if="dot"
55
+ class="rounded-full flex-shrink-0"
56
+ :style="{
57
+ width: `${s.dotSize}px`,
58
+ height: `${s.dotSize}px`,
59
+ background: v.dot,
60
+ }"
61
+ />
62
+ <slot />
63
+ </span>
64
+ </template>
@@ -0,0 +1,161 @@
1
+ <script setup lang="ts">
2
+ import { computed } 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
+ outline: {
71
+ background: "transparent",
72
+ color: "var(--accent, #12675D)",
73
+ border: "1px solid var(--accent, #12675D)",
74
+ hoverBg: "var(--accent-bg, rgba(18, 103, 93, .1))",
75
+ },
76
+ link: {
77
+ background: "transparent",
78
+ color: "var(--accent, #12675D)",
79
+ border: "1px solid transparent",
80
+ paddingLeft: "4px",
81
+ paddingRight: "4px",
82
+ boxShadow: "none",
83
+ },
84
+ "no-line": {
85
+ background: "none",
86
+ border: "none",
87
+ boxShadow: "none",
88
+ color: "var(--t3, #8a96b0)",
89
+ paddingLeft: "14px",
90
+ paddingRight: "14px",
91
+ borderRadius: "var(--rmd, 8px)",
92
+ },
93
+ };
94
+
95
+ const SIZES: Record<string, any> = {
96
+ xs: { padding: "0 6px", fontSize: 11, height: 24 },
97
+ sm: { padding: "0 8px", fontSize: 11.5, height: 28 },
98
+ md: { padding: "0 14px", fontSize: 13, height: 32 },
99
+ lg: { padding: "0 18px", fontSize: 14, height: 36 },
100
+ };
101
+
102
+ const props = withDefaults(
103
+ defineProps<{
104
+ variant?: string;
105
+ size?: "xs" | "sm" | "md" | "lg";
106
+ disabled?: boolean;
107
+ loading?: boolean;
108
+ type?: "button" | "submit" | "reset";
109
+ }>(),
110
+ {
111
+ variant: "default",
112
+ size: "md",
113
+ disabled: false,
114
+ loading: false,
115
+ type: "button",
116
+ }
117
+ );
118
+
119
+ const emit = defineEmits<{
120
+ (e: "click", event: MouseEvent): void;
121
+ }>();
122
+
123
+ const v = computed(() => VARIANTS[props.variant] || VARIANTS.default);
124
+ const s = computed(() => SIZES[props.size] || SIZES.md);
125
+ const isIconOnly = computed(() => props.variant === "icon");
126
+ </script>
127
+
128
+ <template>
129
+ <button
130
+ :type="type"
131
+ :disabled="disabled || loading"
132
+ :class="[`btn btn-${variant}`, 'inline-flex items-center justify-center gap-1.5 font-medium transition select-none']"
133
+ :style="{
134
+ borderRadius: 'var(--rads, 8px)',
135
+ fontFamily: 'var(--f, var(--sans, inherit))',
136
+ cursor: disabled ? 'not-allowed' : 'pointer',
137
+ whiteSpace: 'nowrap',
138
+ letterSpacing: '-.01em',
139
+ outline: 'none',
140
+ boxShadow: 'var(--shadow, 0 1px 3px rgba(20,22,41,.1))',
141
+ opacity: disabled ? 0.45 : 1,
142
+ pointerEvents: disabled ? 'none' : undefined,
143
+ background: v.background,
144
+ color: v.color,
145
+ border: v.border,
146
+ padding: isIconOnly ? '0' : s.padding,
147
+ fontSize: `${s.fontSize}px`,
148
+ height: `${s.height}px`,
149
+ width: isIconOnly ? `${s.height}px` : undefined,
150
+ ...(v.paddingLeft !== undefined && { paddingLeft: v.paddingLeft }),
151
+ ...(v.paddingRight !== undefined && { paddingRight: v.paddingRight }),
152
+ ...(v.boxShadow !== undefined && { boxShadow: v.boxShadow }),
153
+ }"
154
+ @click="emit('click', $event)"
155
+ >
156
+ <Spinner v-if="loading" :size="14" inline color="currentColor" />
157
+ <slot v-if="!loading" name="icon" />
158
+ <slot v-if="!loading" />
159
+ <slot v-if="!loading" name="iconRight" />
160
+ </button>
161
+ </template>
@@ -0,0 +1,97 @@
1
+ <script setup lang="ts">
2
+ import { computed } from "vue";
3
+
4
+ const props = withDefaults(
5
+ defineProps<{
6
+ modelValue?: boolean;
7
+ checked?: boolean;
8
+ indeterminate?: boolean;
9
+ disabled?: boolean;
10
+ size?: "sm" | "md";
11
+ label?: string;
12
+ }>(),
13
+ {
14
+ modelValue: false,
15
+ checked: false,
16
+ indeterminate: false,
17
+ disabled: false,
18
+ size: "md",
19
+ label: "",
20
+ }
21
+ );
22
+
23
+ const emit = defineEmits<{
24
+ (e: "update:modelValue", value: boolean): void;
25
+ (e: "change", value: boolean): void;
26
+ }>();
27
+
28
+ const isChecked = computed(() => props.modelValue || props.checked);
29
+ const dims = computed(() =>
30
+ props.size === "sm" ? { w: 14, h: 14, icon: 8 } : { w: 16, h: 16, icon: 10 }
31
+ );
32
+
33
+ const toggle = () => {
34
+ if (props.disabled) return;
35
+ const next = !isChecked.value;
36
+ emit("update:modelValue", next);
37
+ emit("change", next);
38
+ };
39
+ </script>
40
+
41
+ <template>
42
+ <label
43
+ class="inline-flex items-center gap-1.5 cursor-pointer select-none m-0 p-0 leading-none transition"
44
+ :style="{
45
+ opacity: disabled ? 0.45 : 1,
46
+ cursor: disabled ? 'not-allowed' : 'pointer',
47
+ }"
48
+ @click.prevent="toggle"
49
+ >
50
+ <span
51
+ class="relative flex-shrink-0 transition-all duration-150 flex items-center justify-center"
52
+ :style="{
53
+ width: `${dims.w}px`,
54
+ height: `${dims.h}px`,
55
+ border: `1.5px solid ${isChecked || indeterminate ? 'var(--accent, #12675D)' : 'var(--border, #e2e8f0)'}`,
56
+ borderRadius: size === 'sm' ? '3px' : '4px',
57
+ background: isChecked || indeterminate ? 'var(--accent, #12675D)' : 'var(--input-bg, #ffffff)',
58
+ }"
59
+ >
60
+ <!-- Indeterminate dash -->
61
+ <svg
62
+ v-if="indeterminate"
63
+ :width="dims.icon"
64
+ :height="dims.icon"
65
+ viewBox="0 0 10 10"
66
+ fill="none"
67
+ >
68
+ <line x1="2" y1="5" x2="8" y2="5" stroke="white" stroke-width="1.6" stroke-linecap="round" />
69
+ </svg>
70
+
71
+ <!-- Checked checkmark -->
72
+ <svg
73
+ v-else-if="isChecked"
74
+ :width="dims.icon"
75
+ :height="dims.icon"
76
+ viewBox="0 0 10 10"
77
+ fill="none"
78
+ >
79
+ <polyline
80
+ points="1.5,5 4,7.5 8.5,2.5"
81
+ stroke="white"
82
+ stroke-width="1.6"
83
+ stroke-linecap="round"
84
+ stroke-linejoin="round"
85
+ />
86
+ </svg>
87
+ </span>
88
+
89
+ <span
90
+ v-if="label || $slots.default"
91
+ :style="{ fontSize: size === 'sm' ? '.72rem' : '.78rem' }"
92
+ class="text-slate-800 font-semibold"
93
+ >
94
+ <slot>{{ label }}</slot>
95
+ </span>
96
+ </label>
97
+ </template>
@@ -0,0 +1,341 @@
1
+ <script setup lang="ts">
2
+ import { ref, computed, useSlots } from "vue";
3
+
4
+ const SIZES: Record<string, { height: number; fontSize: number; paddingH: number }> = {
5
+ xs: { height: 24, fontSize: 11, paddingH: 6 },
6
+ sm: { height: 28, fontSize: 11.5, paddingH: 8 },
7
+ md: { height: 32, fontSize: 13, paddingH: 11 },
8
+ lg: { height: 36, fontSize: 14, paddingH: 14 },
9
+ };
10
+
11
+ const props = withDefaults(
12
+ defineProps<{
13
+ modelValue?: string | number;
14
+ label?: string;
15
+ required?: boolean;
16
+ hint?: string;
17
+ error?: string;
18
+ size?: "xs" | "sm" | "md" | "lg";
19
+ type?: string;
20
+ icon?: any;
21
+ iconRight?: any;
22
+ clearable?: boolean;
23
+ suffix?: string;
24
+ multiline?: boolean;
25
+ disabled?: boolean;
26
+ placeholder?: string;
27
+ className?: string;
28
+ style?: Record<string, any>;
29
+ }>(),
30
+ {
31
+ modelValue: "",
32
+ required: false,
33
+ size: "md",
34
+ type: "text",
35
+ clearable: false,
36
+ multiline: false,
37
+ disabled: false,
38
+ placeholder: "",
39
+ className: "",
40
+ style: () => ({}),
41
+ }
42
+ );
43
+
44
+ const emit = defineEmits<{
45
+ (e: "update:modelValue", value: string): void;
46
+ (e: "clear"): void;
47
+ (e: "focus", event: FocusEvent): void;
48
+ (e: "blur", event: FocusEvent): void;
49
+ }>();
50
+
51
+ const slots = useSlots();
52
+ const inputRef = ref<HTMLInputElement | HTMLTextAreaElement | null>(null);
53
+ const focused = ref(false);
54
+ const hovered = ref(false);
55
+ const s = computed(() => SIZES[props.size] || SIZES.md);
56
+
57
+ const isPasswd = computed(() => props.type === "passwd");
58
+ const resolvedType = computed(() => (isPasswd.value ? "text" : props.type));
59
+ const hasValue = computed(
60
+ () => props.modelValue !== undefined && props.modelValue !== null && props.modelValue !== ""
61
+ );
62
+
63
+ const passwdStyle = computed(() =>
64
+ isPasswd.value
65
+ ? {
66
+ WebkitTextSecurity: "disc",
67
+ textSecurity: "disc",
68
+ fontFamily: hasValue.value ? "monospace" : "var(--f, var(--sans, inherit))",
69
+ fontWeight: hasValue.value ? 700 : "normal",
70
+ letterSpacing: hasValue.value ? "0.15em" : "normal",
71
+ }
72
+ : {}
73
+ );
74
+
75
+ const hasIconLeft = computed(() => Boolean(props.icon) || Boolean(slots.icon));
76
+ const hasIconRight = computed(() => Boolean(props.iconRight) || Boolean(slots.iconRight));
77
+ const showClear = computed(
78
+ () => props.clearable && Boolean(props.modelValue) && !props.disabled && !props.multiline
79
+ );
80
+ const hasSuffix = computed(() => Boolean(props.suffix) && !props.multiline);
81
+ const showRightSlot = computed(() => showClear.value || hasSuffix.value);
82
+
83
+ const handleInput = (e: Event) => {
84
+ emit("update:modelValue", (e.target as HTMLInputElement).value);
85
+ };
86
+
87
+ const handleClear = () => {
88
+ emit("update:modelValue", "");
89
+ emit("clear");
90
+ inputRef.value?.focus();
91
+ };
92
+ </script>
93
+
94
+ <template>
95
+ <div
96
+ class="cynx-input-wrapper"
97
+ style="display: flex; flex-direction: column; gap: 5px; width: 100%;"
98
+ >
99
+ <label
100
+ v-if="label"
101
+ :style="{
102
+ fontSize: size === 'xs' ? '9px' : '11px',
103
+ fontWeight: 600,
104
+ color: 'var(--t2, #797b8d)',
105
+ textTransform: 'uppercase',
106
+ letterSpacing: '.08em',
107
+ userSelect: 'none',
108
+ }"
109
+ >
110
+ {{ label }}
111
+ <span v-if="required" style="color: var(--danger, #f64747); margin-left: 2px;">*</span>
112
+ </label>
113
+
114
+ <div style="position: relative; display: flex; align-items: center; width: 100%;">
115
+ <!-- Left Icon / Text Prefix -->
116
+ <span
117
+ v-if="hasIconLeft"
118
+ style="
119
+ position: absolute;
120
+ left: 10px;
121
+ top: 50%;
122
+ transform: translateY(-50%);
123
+ color: var(--t3, #9b9daa);
124
+ pointer-events: none;
125
+ line-height: 0;
126
+ display: flex;
127
+ align-items: center;
128
+ justify-content: center;
129
+ z-index: 2;
130
+ "
131
+ >
132
+ <slot name="icon">
133
+ <span
134
+ v-if="typeof icon === 'string'"
135
+ style="font-size: 11px; font-weight: 600; color: var(--t3, #9b9daa); user-select: none;"
136
+ >
137
+ {{ icon }}
138
+ </span>
139
+ <component
140
+ :is="icon"
141
+ v-else
142
+ style="width: 15px; height: 15px; color: var(--t3, #9b9daa);"
143
+ />
144
+ </slot>
145
+ </span>
146
+
147
+ <!-- Right Icon Slot -->
148
+ <span
149
+ v-if="hasIconRight"
150
+ style="
151
+ position: absolute;
152
+ right: 10px;
153
+ top: 50%;
154
+ transform: translateY(-50%);
155
+ color: var(--t3, #9b9daa);
156
+ pointer-events: none;
157
+ display: flex;
158
+ align-items: center;
159
+ justify-content: center;
160
+ z-index: 2;
161
+ "
162
+ >
163
+ <slot name="iconRight">
164
+ <component :is="iconRight" style="width: 15px; height: 15px; color: var(--t3, #9b9daa);" />
165
+ </slot>
166
+ </span>
167
+
168
+ <!-- Textarea Multiline -->
169
+ <textarea
170
+ v-if="multiline"
171
+ ref="inputRef"
172
+ :value="modelValue"
173
+ :placeholder="placeholder"
174
+ :disabled="disabled"
175
+ :class="['cynx-input-control', className]"
176
+ :style="{
177
+ display: 'block',
178
+ width: '100%',
179
+ minHeight: '70px',
180
+ resize: 'vertical',
181
+ whiteSpace: 'pre-wrap',
182
+ overflowWrap: 'break-word',
183
+ background: disabled ? 'var(--grey-1, #f5f5f8)' : 'var(--input-bg, #fff)',
184
+ border: `1px solid ${error ? 'var(--danger, #f64747)' : focused ? 'var(--accent, #12675D)' : hovered ? 'var(--accent, #12675D)' : 'var(--bd, #d7d8e0)'}`,
185
+ borderRadius: 'var(--rads, 8px)',
186
+ color: 'var(--t1, #1a1a2e)',
187
+ fontFamily: 'var(--f, var(--sans, inherit))',
188
+ fontSize: `${s.fontSize}px`,
189
+ paddingTop: '8px',
190
+ paddingBottom: '8px',
191
+ paddingLeft: hasIconLeft ? '34px' : '12px',
192
+ paddingRight: (hasIconRight || showRightSlot) ? '34px' : '12px',
193
+ outline: 'none',
194
+ transition: 'border-color .14s, box-shadow .14s',
195
+ boxShadow: error
196
+ ? 'var(--dd, rgba(246,71,71,.12)), var(--shadow, 0 1px 3px rgba(20,22,41,.1))'
197
+ : focused
198
+ ? 'var(--pd, rgba(18,103,93,.12)), var(--shadow, 0 1px 3px rgba(20,22,41,.1))'
199
+ : 'var(--shadow, 0 1px 3px rgba(20,22,41,.1))',
200
+ opacity: disabled ? 0.5 : 1,
201
+ cursor: disabled ? 'not-allowed' : undefined,
202
+ ...style,
203
+ }"
204
+ @input="handleInput"
205
+ @focus="focused = true; emit('focus', $event)"
206
+ @blur="focused = false; hovered = false; emit('blur', $event)"
207
+ />
208
+
209
+ <!-- Standard Input Control -->
210
+ <input
211
+ v-else
212
+ ref="inputRef"
213
+ :type="resolvedType"
214
+ :value="modelValue"
215
+ :placeholder="placeholder"
216
+ :disabled="disabled"
217
+ :class="['cynx-input-control', className]"
218
+ :style="{
219
+ display: 'block',
220
+ width: '100%',
221
+ background: disabled ? 'var(--grey-1, #f5f5f8)' : 'var(--input-bg, #fff)',
222
+ border: `1px solid ${error ? 'var(--danger, #f64747)' : focused ? 'var(--accent, #12675D)' : hovered ? 'var(--accent, #12675D)' : 'var(--bd, #d7d8e0)'}`,
223
+ borderRadius: 'var(--rads, 8px)',
224
+ color: 'var(--t1, #1a1a2e)',
225
+ fontFamily: 'var(--f, var(--sans, inherit))',
226
+ fontSize: `${s.fontSize}px`,
227
+ height: `${s.height}px`,
228
+ padding: `0 ${s.paddingH}px`,
229
+ paddingLeft: hasIconLeft ? '34px' : `${s.paddingH}px`,
230
+ paddingRight: (hasIconRight || showRightSlot) ? '34px' : `${s.paddingH}px`,
231
+ outline: 'none',
232
+ transition: 'border-color .14s, box-shadow .14s',
233
+ boxShadow: error
234
+ ? 'var(--dd, rgba(246,71,71,.12)), var(--shadow, 0 1px 3px rgba(20,22,41,.1))'
235
+ : focused
236
+ ? 'var(--pd, rgba(18,103,93,.12)), var(--shadow, 0 1px 3px rgba(20,22,41,.1))'
237
+ : 'var(--shadow, 0 1px 3px rgba(20,22,41,.1))',
238
+ opacity: disabled ? 0.5 : 1,
239
+ cursor: disabled ? 'not-allowed' : undefined,
240
+ ...passwdStyle,
241
+ ...style,
242
+ }"
243
+ @input="handleInput"
244
+ @focus="focused = true; emit('focus', $event)"
245
+ @blur="focused = false; hovered = false; emit('blur', $event)"
246
+ />
247
+
248
+ <!-- Clear Button (1-to-1 matching Input.jsx) -->
249
+ <button
250
+ v-if="showClear"
251
+ type="button"
252
+ tabindex="-1"
253
+ style="
254
+ position: absolute;
255
+ right: 8px;
256
+ top: 50%;
257
+ transform: translateY(-50%);
258
+ display: flex;
259
+ align-items: center;
260
+ justify-content: center;
261
+ width: 18px;
262
+ height: 18px;
263
+ border-radius: 50%;
264
+ background: transparent;
265
+ border: none;
266
+ cursor: pointer;
267
+ color: var(--t3, #9b9daa);
268
+ padding: 0;
269
+ line-height: 0;
270
+ z-index: 2;
271
+ "
272
+ title="Clear"
273
+ @click="handleClear"
274
+ >
275
+ <svg
276
+ width="10"
277
+ height="10"
278
+ viewBox="0 0 10 10"
279
+ fill="none"
280
+ stroke="currentColor"
281
+ stroke-width="1.8"
282
+ stroke-linecap="round"
283
+ >
284
+ <line x1="1" y1="1" x2="9" y2="9" />
285
+ <line x1="9" y1="1" x2="1" y2="9" />
286
+ </svg>
287
+ </button>
288
+
289
+ <!-- Suffix Text / Element -->
290
+ <span
291
+ v-if="hasSuffix && !showClear"
292
+ style="
293
+ position: absolute;
294
+ right: 8px;
295
+ top: 50%;
296
+ transform: translateY(-50%);
297
+ display: flex;
298
+ align-items: center;
299
+ gap: 4px;
300
+ pointer-events: none;
301
+ z-index: 2;
302
+ "
303
+ >
304
+ {{ suffix }}
305
+ </span>
306
+ </div>
307
+
308
+ <!-- Hint / Error Messages -->
309
+ <span
310
+ v-if="hint && !error"
311
+ style="font-size: 11px; color: var(--t3, #9b9daa);"
312
+ >
313
+ {{ hint }}
314
+ </span>
315
+ <span
316
+ v-if="error"
317
+ style="font-size: 11px; color: var(--danger, #f64747);"
318
+ >
319
+ {{ error }}
320
+ </span>
321
+ </div>
322
+ </template>
323
+
324
+ <style scoped>
325
+ .cynx-input-control::placeholder {
326
+ color: var(--t3, #9b9daa);
327
+ opacity: 1;
328
+ }
329
+
330
+ :root.dark .cynx-input-control,
331
+ :root.dark2 .cynx-input-control {
332
+ --input-bg: #141629;
333
+ --t1: #ffffff;
334
+ --bd: #334155;
335
+ }
336
+
337
+ :root.dark .cynx-input-control::placeholder,
338
+ :root.dark2 .cynx-input-control::placeholder {
339
+ color: #64748b;
340
+ }
341
+ </style>