cynx-lib 1.3.28
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/index.js +3 -0
- package/package.json +36 -0
- package/ui/components/ActionsMenu.jsx +158 -0
- package/ui/components/ActivityCard.jsx +184 -0
- package/ui/components/AuthOverlay.jsx +94 -0
- package/ui/components/Badge.jsx +186 -0
- package/ui/components/BrandingCard.jsx +146 -0
- package/ui/components/Button.jsx +148 -0
- package/ui/components/Card.jsx +132 -0
- package/ui/components/Checkbox.jsx +59 -0
- package/ui/components/ChipSelect.jsx +54 -0
- package/ui/components/CircularProgress.jsx +216 -0
- package/ui/components/Confirm.jsx +79 -0
- package/ui/components/DatePicker.jsx +605 -0
- package/ui/components/Divider.jsx +96 -0
- package/ui/components/Dropzone.jsx +203 -0
- package/ui/components/FilePicker.jsx +205 -0
- package/ui/components/HoverCard.jsx +81 -0
- package/ui/components/ImageUpload.jsx +147 -0
- package/ui/components/InfoBadge.jsx +204 -0
- package/ui/components/Input.jsx +229 -0
- package/ui/components/LineProgress.jsx +133 -0
- package/ui/components/LinearProgress.jsx +132 -0
- package/ui/components/Modal.jsx +212 -0
- package/ui/components/Pagination.jsx +52 -0
- package/ui/components/PulseDot.jsx +77 -0
- package/ui/components/Select.jsx +415 -0
- package/ui/components/Skeleton.jsx +118 -0
- package/ui/components/Spinner.jsx +108 -0
- package/ui/components/StatBarCard.jsx +25 -0
- package/ui/components/StatsBar.jsx +88 -0
- package/ui/components/StatsCard.jsx +171 -0
- package/ui/components/TabBar.jsx +404 -0
- package/ui/components/Tabs.jsx +29 -0
- package/ui/components/TimePicker.jsx +1069 -0
- package/ui/components/Toast.jsx +106 -0
- package/ui/components/Toggle.jsx +67 -0
- package/ui/components/UptimeBar.jsx +135 -0
- package/ui/components/XTable.jsx +44 -0
- package/ui/index.js +97 -0
- package/utils/index.js +11 -0
- package/utils/utils/oidc.js +209 -0
- package/utils/utils/realtime.js +1040 -0
- package/utils/utils/storage.js +393 -0
- package/utils/utils/tz.js +625 -0
- package/utils/utils/uid.js +252 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { useState, useRef, useEffect } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
3
|
+
import { Pencil, Upload, X, Trash2 } from 'lucide-react';
|
|
4
|
+
import Button from './Button.jsx';
|
|
5
|
+
|
|
6
|
+
export default function ImageUpload({ value, file, onChange, onRemove, label = 'Image' }) {
|
|
7
|
+
const [editOpen, setEditOpen] = useState(false);
|
|
8
|
+
const [urlInput, setUrlInput] = useState(value || '');
|
|
9
|
+
const [pendingFile, setPendingFile] = useState(null);
|
|
10
|
+
const [dragOver, setDragOver] = useState(false);
|
|
11
|
+
const [closing, setClosing] = useState(false);
|
|
12
|
+
|
|
13
|
+
const previewUrl = file ? URL.createObjectURL(file) : value;
|
|
14
|
+
const filename = file ? file.name : value ? value.split('/').pop() : null;
|
|
15
|
+
const hasImage = !!(file || value);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!editOpen) return;
|
|
19
|
+
function onKey(e) { if (e.key === 'Escape') handleClose(); }
|
|
20
|
+
document.addEventListener('keydown', onKey);
|
|
21
|
+
return () => document.removeEventListener('keydown', onKey);
|
|
22
|
+
}, [editOpen]);
|
|
23
|
+
|
|
24
|
+
function handleFile(f) {
|
|
25
|
+
if (f && f.type.startsWith('image/')) {
|
|
26
|
+
setPendingFile(f);
|
|
27
|
+
setUrlInput('');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function handleSave() {
|
|
32
|
+
if (pendingFile) {
|
|
33
|
+
onChange({ file: pendingFile, url: '' });
|
|
34
|
+
} else if (urlInput.trim()) {
|
|
35
|
+
onChange({ file: null, url: urlInput.trim() });
|
|
36
|
+
}
|
|
37
|
+
handleClose();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function handleClose() {
|
|
41
|
+
setClosing(true);
|
|
42
|
+
setTimeout(() => {
|
|
43
|
+
setEditOpen(false);
|
|
44
|
+
setClosing(false);
|
|
45
|
+
setPendingFile(null);
|
|
46
|
+
setUrlInput(value || '');
|
|
47
|
+
}, 260);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function openEdit() {
|
|
51
|
+
setUrlInput(value || '');
|
|
52
|
+
setPendingFile(null);
|
|
53
|
+
setEditOpen(true);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const modal = editOpen && createPortal(
|
|
57
|
+
<div className="modal-backdrop"
|
|
58
|
+
style={{ animation: `${closing ? 'backdropClose' : 'backdropFade'} .26s ease forwards` }}
|
|
59
|
+
onClick={e => { if (e.target === e.currentTarget) handleClose(); }}>
|
|
60
|
+
<div className="modal" style={{
|
|
61
|
+
maxWidth: 420,
|
|
62
|
+
animation: `${closing ? 'modalClose' : 'modalPop'} .26s cubic-bezier(.4,0,.2,1) forwards`,
|
|
63
|
+
}}>
|
|
64
|
+
<div className="modal-hdr">
|
|
65
|
+
<div className="modal-title">Edit {label}</div>
|
|
66
|
+
<Button variant="no-line" size="sm" icon={<X size={14} />} onClick={handleClose} />
|
|
67
|
+
</div>
|
|
68
|
+
<div className="modal-body" style={{ overflow: 'visible' }}>
|
|
69
|
+
<label
|
|
70
|
+
style={{
|
|
71
|
+
display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
|
|
72
|
+
gap: 8, padding: '24px 16px', borderRadius: 'var(--rads, 8px)',
|
|
73
|
+
border: `1.5px dashed ${dragOver ? 'var(--accent, #f99e2c)' : 'var(--border, #e2e8f0)'}`,
|
|
74
|
+
background: 'var(--grey-1, #f5f5f8)', cursor: 'pointer', transition: 'all .15s', textAlign: 'center',
|
|
75
|
+
}}
|
|
76
|
+
onDragOver={e => { e.preventDefault(); setDragOver(true); }}
|
|
77
|
+
onDragLeave={() => setDragOver(false)}
|
|
78
|
+
onDrop={e => { e.preventDefault(); setDragOver(false); handleFile(e.dataTransfer.files[0]); }}
|
|
79
|
+
>
|
|
80
|
+
<input type="file" accept="image/*,.ico" style={{ display: 'none' }}
|
|
81
|
+
onChange={e => handleFile(e.target.files[0])} />
|
|
82
|
+
{pendingFile ? (
|
|
83
|
+
<>
|
|
84
|
+
<img src={URL.createObjectURL(pendingFile)} alt="preview" style={{ height: 48, maxWidth: 160, objectFit: 'contain', borderRadius: 4 }} />
|
|
85
|
+
<div style={{ fontSize: '.76rem', fontWeight: 600, color: 'var(--accent, #f99e2c)' }}>{pendingFile.name}</div>
|
|
86
|
+
<div style={{ fontSize: '.68rem', color: 'var(--muted, #797b8d)' }}>{(pendingFile.size / 1024).toFixed(1)} KB — click to change</div>
|
|
87
|
+
</>
|
|
88
|
+
) : (
|
|
89
|
+
<>
|
|
90
|
+
<Upload size={20} style={{ color: 'var(--muted, #797b8d)' }} />
|
|
91
|
+
<div style={{ fontSize: '.82rem', fontWeight: 600 }}>Click or drag to upload</div>
|
|
92
|
+
<div style={{ fontSize: '.72rem', color: 'var(--muted, #797b8d)' }}>PNG, JPG, SVG, ICO up to 2MB</div>
|
|
93
|
+
</>
|
|
94
|
+
)}
|
|
95
|
+
</label>
|
|
96
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, margin: '12px 0 4px' }}>
|
|
97
|
+
<div style={{ flex: 1, height: 1, background: 'var(--border, #e2e8f0)' }} />
|
|
98
|
+
<span style={{ fontSize: '.72rem', color: 'var(--muted, #797b8d)' }}>or enter URL directly</span>
|
|
99
|
+
<div style={{ flex: 1, height: 1, background: 'var(--border, #e2e8f0)' }} />
|
|
100
|
+
</div>
|
|
101
|
+
<input
|
|
102
|
+
type="url" placeholder="https://example.com/logo.png"
|
|
103
|
+
value={urlInput}
|
|
104
|
+
onChange={e => { setUrlInput(e.target.value); setPendingFile(null); }}
|
|
105
|
+
style={{
|
|
106
|
+
width: '100%', padding: '8px 10px', fontSize: '.82rem',
|
|
107
|
+
border: '1px solid var(--border, #e2e8f0)', borderRadius: 'var(--rads, 8px)',
|
|
108
|
+
background: 'var(--input-bg, #ffffff)', color: 'var(--primary-text, #0f172a)',
|
|
109
|
+
fontFamily: 'var(--sans, inherit)', outline: 'none',
|
|
110
|
+
}}
|
|
111
|
+
/>
|
|
112
|
+
</div>
|
|
113
|
+
<div className="modal-footer">
|
|
114
|
+
<Button variant="ghost" size="xs" onClick={handleClose}>Cancel</Button>
|
|
115
|
+
<Button variant="primary" size="xs" onClick={handleSave} disabled={!pendingFile && !urlInput.trim()}>Save</Button>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
</div>,
|
|
119
|
+
document.body
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<>
|
|
124
|
+
<div style={{
|
|
125
|
+
display: 'flex', alignItems: 'center', gap: 12,
|
|
126
|
+
padding: '10px 12px', borderRadius: 'var(--rads, 8px)',
|
|
127
|
+
border: '1px solid var(--border, #e2e8f0)', background: 'var(--grey-1, #f5f5f8)',
|
|
128
|
+
}}>
|
|
129
|
+
{hasImage ? (
|
|
130
|
+
<img src={previewUrl} alt={label} style={{ height: 32, maxWidth: 80, objectFit: 'contain', borderRadius: 4, flexShrink: 0 }} />
|
|
131
|
+
) : (
|
|
132
|
+
<div style={{ width: 32, height: 32, borderRadius: 4, background: 'var(--grey-2, #ebebee)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
|
|
133
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--muted, #797b8d)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
|
|
134
|
+
</div>
|
|
135
|
+
)}
|
|
136
|
+
<div style={{ flex: 1, fontSize: '.78rem', color: filename ? 'var(--primary-text, #0f172a)' : 'var(--muted, #797b8d)', fontFamily: filename ? 'var(--mono, ui-monospace, monospace)' : 'inherit', wordBreak: 'break-all' }}>
|
|
137
|
+
{filename || 'Not set yet'}
|
|
138
|
+
</div>
|
|
139
|
+
<Button variant="ghost" size="xs" icon={<Pencil size={12} />} onClick={openEdit} style={{ flexShrink: 0 }}>Edit</Button>
|
|
140
|
+
{hasImage && (
|
|
141
|
+
<Button variant="ghost" size="xs" icon={<Trash2 size={12} />} style={{ color: 'var(--danger, #f64747)', flexShrink: 0 }} onClick={onRemove}>Remove</Button>
|
|
142
|
+
)}
|
|
143
|
+
</div>
|
|
144
|
+
{modal}
|
|
145
|
+
</>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
const SIZES = {
|
|
4
|
+
xs: {
|
|
5
|
+
height: 26,
|
|
6
|
+
padding: '0 8px',
|
|
7
|
+
fontSize: '.74rem',
|
|
8
|
+
iconSize: 13,
|
|
9
|
+
dotSize: '.7rem',
|
|
10
|
+
sepHeight: 12,
|
|
11
|
+
gap: 6,
|
|
12
|
+
borderRadius: 'var(--radxs, 6px)',
|
|
13
|
+
},
|
|
14
|
+
sm: {
|
|
15
|
+
height: 32,
|
|
16
|
+
padding: '0 12px',
|
|
17
|
+
fontSize: '.82rem',
|
|
18
|
+
iconSize: 15,
|
|
19
|
+
dotSize: '.75rem',
|
|
20
|
+
sepHeight: 14,
|
|
21
|
+
gap: 8,
|
|
22
|
+
borderRadius: 'var(--rads, 8px)',
|
|
23
|
+
},
|
|
24
|
+
md: {
|
|
25
|
+
height: 36,
|
|
26
|
+
padding: '0 14px',
|
|
27
|
+
fontSize: '.88rem',
|
|
28
|
+
iconSize: 16,
|
|
29
|
+
dotSize: '.8rem',
|
|
30
|
+
sepHeight: 16,
|
|
31
|
+
gap: 10,
|
|
32
|
+
borderRadius: 'var(--rad, 10px)',
|
|
33
|
+
},
|
|
34
|
+
lg: {
|
|
35
|
+
height: 40,
|
|
36
|
+
padding: '0 16px',
|
|
37
|
+
fontSize: '.95rem',
|
|
38
|
+
iconSize: 18,
|
|
39
|
+
dotSize: '.85rem',
|
|
40
|
+
sepHeight: 18,
|
|
41
|
+
gap: 12,
|
|
42
|
+
borderRadius: 'var(--rad, 10px)',
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const VARIANTS = {
|
|
47
|
+
default: {
|
|
48
|
+
background: 'var(--surface, #ffffff)',
|
|
49
|
+
border: '1px solid var(--border, #d7d8e0)',
|
|
50
|
+
boxShadow: 'var(--shadow, 0 1px 2px rgba(0, 0, 0, 0.05))',
|
|
51
|
+
},
|
|
52
|
+
surface: {
|
|
53
|
+
background: 'var(--surface, #ffffff)',
|
|
54
|
+
border: '1px solid var(--border, #d7d8e0)',
|
|
55
|
+
boxShadow: 'var(--shadow, 0 1px 2px rgba(0, 0, 0, 0.05))',
|
|
56
|
+
},
|
|
57
|
+
flat: {
|
|
58
|
+
background: 'var(--grey-1, #f5f5f8)',
|
|
59
|
+
border: '1px solid var(--border, #d7d8e0)',
|
|
60
|
+
boxShadow: 'none',
|
|
61
|
+
},
|
|
62
|
+
ghost: {
|
|
63
|
+
background: 'transparent',
|
|
64
|
+
border: '1px solid var(--border, #d7d8e0)',
|
|
65
|
+
boxShadow: 'none',
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export default function InfoBadge({
|
|
70
|
+
items,
|
|
71
|
+
icon,
|
|
72
|
+
label,
|
|
73
|
+
value,
|
|
74
|
+
color,
|
|
75
|
+
title,
|
|
76
|
+
dot = true,
|
|
77
|
+
size = 'sm',
|
|
78
|
+
variant = 'default',
|
|
79
|
+
style,
|
|
80
|
+
className = '',
|
|
81
|
+
children,
|
|
82
|
+
onClick,
|
|
83
|
+
...props
|
|
84
|
+
}) {
|
|
85
|
+
const sz = SIZES[size] || SIZES.sm;
|
|
86
|
+
const vr = VARIANTS[variant] || VARIANTS.default;
|
|
87
|
+
|
|
88
|
+
const containerStyle = {
|
|
89
|
+
display: 'inline-flex',
|
|
90
|
+
alignItems: 'center',
|
|
91
|
+
boxSizing: 'border-box',
|
|
92
|
+
height: sz.height,
|
|
93
|
+
padding: sz.padding,
|
|
94
|
+
borderRadius: sz.borderRadius,
|
|
95
|
+
background: vr.background,
|
|
96
|
+
border: vr.border,
|
|
97
|
+
boxShadow: vr.boxShadow,
|
|
98
|
+
gap: sz.gap,
|
|
99
|
+
cursor: onClick ? 'pointer' : 'default',
|
|
100
|
+
userSelect: 'none',
|
|
101
|
+
...style,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
if (children) {
|
|
105
|
+
return (
|
|
106
|
+
<div style={containerStyle} className={className} onClick={onClick} title={title} {...props}>
|
|
107
|
+
{children}
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const resolvedItems = Array.isArray(items) && items.length > 0
|
|
113
|
+
? items
|
|
114
|
+
: (icon != null || value != null || label != null)
|
|
115
|
+
? [{ icon, label, value, color, title, onClick }]
|
|
116
|
+
: [];
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<div style={containerStyle} className={className} onClick={onClick} title={title} {...props}>
|
|
120
|
+
{resolvedItems.map((it, idx) => {
|
|
121
|
+
const itemColor = it.color || 'inherit';
|
|
122
|
+
const showDot = it.dot !== undefined ? it.dot : dot;
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<React.Fragment key={it.id || it.key || idx}>
|
|
126
|
+
{idx > 0 && (
|
|
127
|
+
<div
|
|
128
|
+
style={{
|
|
129
|
+
width: 1,
|
|
130
|
+
height: sz.sepHeight,
|
|
131
|
+
background: 'var(--border, #d7d8e0)',
|
|
132
|
+
flexShrink: 0,
|
|
133
|
+
}}
|
|
134
|
+
/>
|
|
135
|
+
)}
|
|
136
|
+
<div
|
|
137
|
+
style={{
|
|
138
|
+
display: 'inline-flex',
|
|
139
|
+
alignItems: 'center',
|
|
140
|
+
gap: 5,
|
|
141
|
+
cursor: it.onClick ? 'pointer' : 'inherit',
|
|
142
|
+
}}
|
|
143
|
+
title={it.title}
|
|
144
|
+
onClick={it.onClick}
|
|
145
|
+
>
|
|
146
|
+
{it.icon && (
|
|
147
|
+
<span
|
|
148
|
+
style={{
|
|
149
|
+
display: 'inline-flex',
|
|
150
|
+
alignItems: 'center',
|
|
151
|
+
justifyContent: 'center',
|
|
152
|
+
color: itemColor,
|
|
153
|
+
flexShrink: 0,
|
|
154
|
+
}}
|
|
155
|
+
>
|
|
156
|
+
{typeof it.icon === 'function'
|
|
157
|
+
? React.createElement(it.icon, { size: sz.iconSize })
|
|
158
|
+
: React.isValidElement(it.icon)
|
|
159
|
+
? React.cloneElement(it.icon, { size: it.icon.props?.size || sz.iconSize })
|
|
160
|
+
: it.icon}
|
|
161
|
+
</span>
|
|
162
|
+
)}
|
|
163
|
+
{it.label && (
|
|
164
|
+
<span
|
|
165
|
+
style={{
|
|
166
|
+
fontSize: sz.fontSize,
|
|
167
|
+
fontWeight: 500,
|
|
168
|
+
color: itemColor !== 'inherit' ? itemColor : 'var(--primary-text, #0f111c)',
|
|
169
|
+
}}
|
|
170
|
+
>
|
|
171
|
+
{it.label}
|
|
172
|
+
</span>
|
|
173
|
+
)}
|
|
174
|
+
{showDot && (it.icon || it.label) && it.value !== undefined && (
|
|
175
|
+
<span
|
|
176
|
+
style={{
|
|
177
|
+
fontSize: sz.dotSize,
|
|
178
|
+
color: 'var(--muted, #9b9daa)',
|
|
179
|
+
lineHeight: 1,
|
|
180
|
+
}}
|
|
181
|
+
>
|
|
182
|
+
•
|
|
183
|
+
</span>
|
|
184
|
+
)}
|
|
185
|
+
{it.value !== undefined && (
|
|
186
|
+
<span
|
|
187
|
+
style={{
|
|
188
|
+
fontSize: sz.fontSize,
|
|
189
|
+
fontWeight: 500,
|
|
190
|
+
color: 'var(--primary-text, #0f111c)',
|
|
191
|
+
fontVariantNumeric: 'tabular-nums',
|
|
192
|
+
lineHeight: 1,
|
|
193
|
+
}}
|
|
194
|
+
>
|
|
195
|
+
{it.value}
|
|
196
|
+
</span>
|
|
197
|
+
)}
|
|
198
|
+
</div>
|
|
199
|
+
</React.Fragment>
|
|
200
|
+
);
|
|
201
|
+
})}
|
|
202
|
+
</div>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
const SIZES = {
|
|
4
|
+
xs: { height: 24, fontSize: 11, paddingH: 6 },
|
|
5
|
+
sm: { height: 28, fontSize: 11.5, paddingH: 8 },
|
|
6
|
+
md: { height: 32, fontSize: 13, paddingH: 11 },
|
|
7
|
+
lg: { height: 36, fontSize: 14, paddingH: 14 },
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default function Input({
|
|
11
|
+
label,
|
|
12
|
+
required = false,
|
|
13
|
+
hint,
|
|
14
|
+
error,
|
|
15
|
+
size = 'md',
|
|
16
|
+
icon,
|
|
17
|
+
iconRight,
|
|
18
|
+
clearable = false,
|
|
19
|
+
suffix,
|
|
20
|
+
multiline = false,
|
|
21
|
+
onClear,
|
|
22
|
+
className = '',
|
|
23
|
+
style = {},
|
|
24
|
+
value,
|
|
25
|
+
...props
|
|
26
|
+
}) {
|
|
27
|
+
const inputRef = useRef(null);
|
|
28
|
+
const [focused, setFocused] = useState(false);
|
|
29
|
+
const [hovered, setHovered] = useState(false);
|
|
30
|
+
const s = SIZES[size] || SIZES.md;
|
|
31
|
+
|
|
32
|
+
function handleClear() {
|
|
33
|
+
if (props.onChange) props.onChange({ target: { value: '' } });
|
|
34
|
+
if (onClear) onClear();
|
|
35
|
+
inputRef.current?.focus();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const hasIconLeft = !!icon;
|
|
39
|
+
const hasIconRight = !!iconRight;
|
|
40
|
+
const showClear = clearable && !!value && !props.disabled && !multiline;
|
|
41
|
+
const hasSuffix = !!suffix && !multiline;
|
|
42
|
+
const showRightSlot = showClear || hasSuffix;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div style={{
|
|
46
|
+
display: 'flex',
|
|
47
|
+
flexDirection: 'column',
|
|
48
|
+
gap: 5,
|
|
49
|
+
}}>
|
|
50
|
+
{label && (
|
|
51
|
+
<label style={{
|
|
52
|
+
fontSize: size === 'xs' ? 9 : 11,
|
|
53
|
+
fontWeight: 600,
|
|
54
|
+
color: 'var(--t2,#797b8d)',
|
|
55
|
+
textTransform: 'uppercase',
|
|
56
|
+
letterSpacing: '.08em',
|
|
57
|
+
}}>
|
|
58
|
+
{label}
|
|
59
|
+
{required && <span style={{ color: 'var(--danger,#f64747)', marginLeft: 2 }}>*</span>}
|
|
60
|
+
</label>
|
|
61
|
+
)}
|
|
62
|
+
|
|
63
|
+
<div style={{
|
|
64
|
+
position: 'relative',
|
|
65
|
+
display: 'flex',
|
|
66
|
+
alignItems: 'center',
|
|
67
|
+
}}>
|
|
68
|
+
{hasIconLeft && (
|
|
69
|
+
<span style={{
|
|
70
|
+
position: 'absolute',
|
|
71
|
+
left: 10,
|
|
72
|
+
top: '50%',
|
|
73
|
+
transform: 'translateY(-50%)',
|
|
74
|
+
color: 'var(--t3,#9b9daa)',
|
|
75
|
+
pointerEvents: 'none',
|
|
76
|
+
lineHeight: 0,
|
|
77
|
+
display: 'flex',
|
|
78
|
+
alignItems: 'center',
|
|
79
|
+
}}>
|
|
80
|
+
{icon}
|
|
81
|
+
</span>
|
|
82
|
+
)}
|
|
83
|
+
|
|
84
|
+
{hasIconRight && (
|
|
85
|
+
<span style={{
|
|
86
|
+
position: 'absolute',
|
|
87
|
+
right: 10,
|
|
88
|
+
top: '50%',
|
|
89
|
+
transform: 'translateY(-50%)',
|
|
90
|
+
color: 'var(--t3,#9b9daa)',
|
|
91
|
+
pointerEvents: 'none',
|
|
92
|
+
display: 'flex',
|
|
93
|
+
alignItems: 'center',
|
|
94
|
+
}}>
|
|
95
|
+
{iconRight}
|
|
96
|
+
</span>
|
|
97
|
+
)}
|
|
98
|
+
|
|
99
|
+
{multiline ? (
|
|
100
|
+
<textarea
|
|
101
|
+
ref={inputRef}
|
|
102
|
+
className={className}
|
|
103
|
+
style={{
|
|
104
|
+
display: 'block',
|
|
105
|
+
width: '100%',
|
|
106
|
+
minHeight: 70,
|
|
107
|
+
resize: 'vertical',
|
|
108
|
+
whiteSpace: 'pre-wrap',
|
|
109
|
+
overflowWrap: 'break-word',
|
|
110
|
+
background: props.disabled ? 'var(--grey-1,#f5f5f8)' : 'var(--input-bg,#fff)',
|
|
111
|
+
border: `1px solid ${error ? 'var(--danger,#f64747)' : focused ? 'var(--accent,#f99e2c)' : hovered ? 'var(--accent,#f99e2c)' : 'var(--bd,#d7d8e0)'}`,
|
|
112
|
+
borderRadius: 'var(--rads,8px)',
|
|
113
|
+
color: 'var(--t1,#1a1a2e)',
|
|
114
|
+
fontFamily: 'var(--f,var(--sans,inherit))',
|
|
115
|
+
fontSize: s.fontSize,
|
|
116
|
+
padding: 0,
|
|
117
|
+
paddingTop: 8,
|
|
118
|
+
paddingBottom: 8,
|
|
119
|
+
paddingLeft: hasIconLeft ? 34 : 12,
|
|
120
|
+
paddingRight: (hasIconRight || showRightSlot) ? 34 : 12,
|
|
121
|
+
outline: 'none',
|
|
122
|
+
transition: 'border-color .14s, box-shadow .14s',
|
|
123
|
+
boxShadow: error
|
|
124
|
+
? 'var(--dd, rgba(246,71,71,.12)), var(--shadow,0 1px 3px rgba(20,22,41,.1))'
|
|
125
|
+
: focused
|
|
126
|
+
? 'var(--pd, rgba(249,158,44,.12)), var(--shadow,0 1px 3px rgba(20,22,41,.1))'
|
|
127
|
+
: 'var(--shadow,0 1px 3px rgba(20,22,41,.1))',
|
|
128
|
+
opacity: props.disabled ? 0.5 : 1,
|
|
129
|
+
cursor: props.disabled ? 'not-allowed' : undefined,
|
|
130
|
+
...style,
|
|
131
|
+
}}
|
|
132
|
+
value={value}
|
|
133
|
+
{...props}
|
|
134
|
+
onFocus={e => { setFocused(true); props.onFocus?.(e); }}
|
|
135
|
+
onBlur={e => { setFocused(false); setHovered(false); props.onBlur?.(e); }}
|
|
136
|
+
/>
|
|
137
|
+
) : (
|
|
138
|
+
<input
|
|
139
|
+
ref={inputRef}
|
|
140
|
+
className={className}
|
|
141
|
+
style={{
|
|
142
|
+
display: 'block',
|
|
143
|
+
width: '100%',
|
|
144
|
+
background: props.disabled ? 'var(--grey-1,#f5f5f8)' : 'var(--input-bg,#fff)',
|
|
145
|
+
border: `1px solid ${error ? 'var(--danger,#f64747)' : focused ? 'var(--accent,#f99e2c)' : hovered ? 'var(--accent,#f99e2c)' : 'var(--bd,#d7d8e0)'}`,
|
|
146
|
+
borderRadius: 'var(--rads,8px)',
|
|
147
|
+
color: 'var(--t1,#1a1a2e)',
|
|
148
|
+
fontFamily: 'var(--f,var(--sans,inherit))',
|
|
149
|
+
fontSize: s.fontSize,
|
|
150
|
+
height: s.height,
|
|
151
|
+
padding: `0 ${s.paddingH}px`,
|
|
152
|
+
paddingLeft: hasIconLeft ? 34 : s.paddingH,
|
|
153
|
+
paddingRight: (hasIconRight || showRightSlot) ? 34 : s.paddingH,
|
|
154
|
+
outline: 'none',
|
|
155
|
+
transition: 'border-color .14s, box-shadow .14s',
|
|
156
|
+
boxShadow: error
|
|
157
|
+
? 'var(--dd, rgba(246,71,71,.12)), var(--shadow,0 1px 3px rgba(20,22,41,.1))'
|
|
158
|
+
: focused
|
|
159
|
+
? 'var(--pd, rgba(249,158,44,.12)), var(--shadow,0 1px 3px rgba(20,22,41,.1))'
|
|
160
|
+
: 'var(--shadow,0 1px 3px rgba(20,22,41,.1))',
|
|
161
|
+
opacity: props.disabled ? 0.5 : 1,
|
|
162
|
+
cursor: props.disabled ? 'not-allowed' : undefined,
|
|
163
|
+
...style,
|
|
164
|
+
}}
|
|
165
|
+
value={value}
|
|
166
|
+
{...props}
|
|
167
|
+
onFocus={e => { setFocused(true); props.onFocus?.(e); }}
|
|
168
|
+
onBlur={e => { setFocused(false); setHovered(false); props.onBlur?.(e); }}
|
|
169
|
+
// onMouseEnter={() => setHovered(true)}
|
|
170
|
+
// onMouseLeave={() => setHovered(false)}
|
|
171
|
+
/>
|
|
172
|
+
)}
|
|
173
|
+
|
|
174
|
+
{showClear && (
|
|
175
|
+
<button
|
|
176
|
+
type="button"
|
|
177
|
+
tabIndex={-1}
|
|
178
|
+
onClick={handleClear}
|
|
179
|
+
style={{
|
|
180
|
+
position: 'absolute',
|
|
181
|
+
right: 8,
|
|
182
|
+
top: '50%',
|
|
183
|
+
transform: 'translateY(-50%)',
|
|
184
|
+
display: 'flex',
|
|
185
|
+
alignItems: 'center',
|
|
186
|
+
justifyContent: 'center',
|
|
187
|
+
width: 18,
|
|
188
|
+
height: 18,
|
|
189
|
+
borderRadius: '50%',
|
|
190
|
+
background: 'transparent',
|
|
191
|
+
border: 'none',
|
|
192
|
+
cursor: 'pointer',
|
|
193
|
+
color: 'var(--t3,#9b9daa)',
|
|
194
|
+
padding: 0,
|
|
195
|
+
lineHeight: 0,
|
|
196
|
+
}}
|
|
197
|
+
>
|
|
198
|
+
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round">
|
|
199
|
+
<line x1="1" y1="1" x2="9" y2="9" />
|
|
200
|
+
<line x1="9" y1="1" x2="1" y2="9" />
|
|
201
|
+
</svg>
|
|
202
|
+
</button>
|
|
203
|
+
)}
|
|
204
|
+
|
|
205
|
+
{hasSuffix && !showClear && (
|
|
206
|
+
<span style={{
|
|
207
|
+
position: 'absolute',
|
|
208
|
+
right: 8,
|
|
209
|
+
top: '50%',
|
|
210
|
+
transform: 'translateY(-50%)',
|
|
211
|
+
display: 'flex',
|
|
212
|
+
alignItems: 'center',
|
|
213
|
+
gap: 4,
|
|
214
|
+
pointerEvents: 'none',
|
|
215
|
+
}}>
|
|
216
|
+
{suffix}
|
|
217
|
+
</span>
|
|
218
|
+
)}
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
{hint && !error && (
|
|
222
|
+
<span style={{ fontSize: 11, color: 'var(--t3,#9b9daa)' }}>{hint}</span>
|
|
223
|
+
)}
|
|
224
|
+
{error && (
|
|
225
|
+
<span style={{ fontSize: 11, color: 'var(--danger,#f64747)' }}>{error}</span>
|
|
226
|
+
)}
|
|
227
|
+
</div>
|
|
228
|
+
);
|
|
229
|
+
}
|