cynx-ui 1.2.7 → 1.2.9
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/components/Badge.jsx +4 -3
- package/components/Modal.jsx +4 -3
- package/components/PulseDot.jsx +4 -3
- package/components/Select.jsx +4 -3
- package/components/Skeleton.jsx +10 -6
- package/components/Spinner.jsx +49 -36
- package/components/Toast.jsx +85 -57
- package/package.json +1 -1
package/components/Badge.jsx
CHANGED
|
@@ -29,14 +29,15 @@ const SHAPES = {
|
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
const KEYFRAMES = `@keyframes badge-pulse{0%,100%{opacity:1}50%{opacity:.4}}`;
|
|
32
|
-
let
|
|
32
|
+
let _id = 'cynx-badge-styles';
|
|
33
33
|
|
|
34
34
|
function ensureStyles() {
|
|
35
|
-
if (
|
|
35
|
+
if (typeof document === 'undefined') return;
|
|
36
|
+
if (document.getElementById(_id)) return;
|
|
36
37
|
const tag = document.createElement('style');
|
|
38
|
+
tag.id = _id;
|
|
37
39
|
tag.textContent = KEYFRAMES;
|
|
38
40
|
document.head.appendChild(tag);
|
|
39
|
-
styleInjected = true;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
const SIZE_MAP = {
|
package/components/Modal.jsx
CHANGED
|
@@ -81,13 +81,14 @@ const STYLES = `
|
|
|
81
81
|
@keyframes modalCardOut { from { opacity:1; transform:scale(1) translateY(0) } to { opacity:0; transform:scale(.96) translateY(-8px) } }
|
|
82
82
|
`;
|
|
83
83
|
|
|
84
|
-
let
|
|
84
|
+
let _id = 'cynx-modal-styles';
|
|
85
85
|
function ensureStyles() {
|
|
86
|
-
if (
|
|
86
|
+
if (typeof document === 'undefined') return;
|
|
87
|
+
if (document.getElementById(_id)) return;
|
|
87
88
|
const tag = document.createElement('style');
|
|
89
|
+
tag.id = _id;
|
|
88
90
|
tag.textContent = STYLES;
|
|
89
91
|
document.head.appendChild(tag);
|
|
90
|
-
injected = true;
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
export function Modal({ open, onClose, onBeforeClose, title, children, footer, maxWidth = 520, bodyStyle }) {
|
package/components/PulseDot.jsx
CHANGED
|
@@ -17,14 +17,15 @@ const SIZES = {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
const KEYFRAMES = `@keyframes pdot-pulse{0%{transform:scale(1);opacity:.7}100%{transform:scale(1.3);opacity:0}}`;
|
|
20
|
-
let
|
|
20
|
+
let _id = 'cynx-pulsedot-styles';
|
|
21
21
|
|
|
22
22
|
function ensureStyles() {
|
|
23
|
-
if (
|
|
23
|
+
if (typeof document === 'undefined') return;
|
|
24
|
+
if (document.getElementById(_id)) return;
|
|
24
25
|
const tag = document.createElement('style');
|
|
26
|
+
tag.id = _id;
|
|
25
27
|
tag.textContent = KEYFRAMES;
|
|
26
28
|
document.head.appendChild(tag);
|
|
27
|
-
styleInjected = true;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
export default function PulseDot({
|
package/components/Select.jsx
CHANGED
|
@@ -3,14 +3,15 @@ import { createPortal } from 'react-dom';
|
|
|
3
3
|
import { ChevronDown, Check, X } from 'lucide-react';
|
|
4
4
|
|
|
5
5
|
const KEYFRAMES = `@keyframes selFadeIn{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}`;
|
|
6
|
-
let
|
|
6
|
+
let _id = 'cynx-select-styles';
|
|
7
7
|
|
|
8
8
|
function ensureStyles() {
|
|
9
|
-
if (
|
|
9
|
+
if (typeof document === 'undefined') return;
|
|
10
|
+
if (document.getElementById(_id)) return;
|
|
10
11
|
const tag = document.createElement('style');
|
|
12
|
+
tag.id = _id;
|
|
11
13
|
tag.textContent = KEYFRAMES;
|
|
12
14
|
document.head.appendChild(tag);
|
|
13
|
-
styleInjected = true;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export default function Select({
|
package/components/Skeleton.jsx
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
const STYLES = `
|
|
2
|
+
@keyframes sk-shimmer{0%{background-position:-600px 0}100%{background-position:600px 0}}
|
|
3
|
+
.skeleton{background:linear-gradient(90deg,var(--grey-1,#f0f1f5) 25%,var(--border-light,#e8e8ec) 50%,var(--grey-1,#f0f1f5) 75%);background-size:600px 100%;animation:sk-shimmer 1.4s ease infinite;border-radius:var(--radxs,4px)}
|
|
4
|
+
.skeleton-text{height:12px;margin-bottom:6px}
|
|
5
|
+
`;
|
|
6
|
+
let _id = 'cynx-skeleton-styles';
|
|
4
7
|
function ensureStyles() {
|
|
5
|
-
if (
|
|
8
|
+
if (typeof document === 'undefined') return;
|
|
9
|
+
if (document.getElementById(_id)) return;
|
|
6
10
|
const tag = document.createElement('style');
|
|
7
|
-
tag.
|
|
11
|
+
tag.id = _id;
|
|
12
|
+
tag.textContent = STYLES;
|
|
8
13
|
document.head.appendChild(tag);
|
|
9
|
-
styleInjected = true;
|
|
10
14
|
}
|
|
11
15
|
|
|
12
16
|
const SK_BASE = {
|
package/components/Spinner.jsx
CHANGED
|
@@ -1,36 +1,49 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
1
|
+
const STYLES = `.spin{animation:cx-spin .7s linear infinite}@keyframes cx-spin{to{transform:rotate(360deg)}}`;
|
|
2
|
+
|
|
3
|
+
let _id = 'cynx-spinner-styles';
|
|
4
|
+
function ensureStyles() {
|
|
5
|
+
if (typeof document === 'undefined') return;
|
|
6
|
+
if (document.getElementById(_id)) return;
|
|
7
|
+
const tag = document.createElement('style');
|
|
8
|
+
tag.id = _id;
|
|
9
|
+
tag.textContent = STYLES;
|
|
10
|
+
document.head.appendChild(tag);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function Spinner({ size = 20, inline = false, color = 'currentColor', fullPage = false }) {
|
|
14
|
+
ensureStyles();
|
|
15
|
+
const s = inline ? 14 : size;
|
|
16
|
+
const svg = (
|
|
17
|
+
<svg className="spin" width={s} height={s} viewBox="0 0 24 24" fill="none"
|
|
18
|
+
stroke={color} strokeWidth="2.5" strokeLinecap="round">
|
|
19
|
+
<path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/>
|
|
20
|
+
</svg>
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
if (fullPage) {
|
|
24
|
+
return (
|
|
25
|
+
<div style={{
|
|
26
|
+
position: 'fixed', inset: 0,
|
|
27
|
+
background: 'var(--bg)',
|
|
28
|
+
display: 'flex', flexDirection: 'column',
|
|
29
|
+
alignItems: 'center', justifyContent: 'center', gap: 16,
|
|
30
|
+
zIndex: 9999,
|
|
31
|
+
}}>
|
|
32
|
+
{svg}
|
|
33
|
+
<div style={{ fontSize: '.75rem', fontFamily: 'var(--mono)', color: 'var(--muted)' }}>
|
|
34
|
+
Loading...
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (inline) return svg;
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '48px 0' }}>
|
|
44
|
+
{svg}
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default Spinner;
|
package/components/Toast.jsx
CHANGED
|
@@ -1,57 +1,85 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
-
import { createPortal } from 'react-dom';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
3
|
+
|
|
4
|
+
const STYLES = `
|
|
5
|
+
#toast-root { position: fixed; bottom: 20px; right: 20px; display: flex; flex-direction: column; gap: 8px; z-index: 99999; }
|
|
6
|
+
.toast {
|
|
7
|
+
background: var(--surface, #fff); border: 1px solid var(--border, #d7d8e0);
|
|
8
|
+
border-radius: var(--rads, 8px); padding: 10px 14px;
|
|
9
|
+
font-size: .78rem; display: flex; align-items: center; gap: 8px;
|
|
10
|
+
box-shadow: 0px 40px 100px 0px rgba(0,0,0,.15); max-width: 300px;
|
|
11
|
+
color: var(--primary-text, #0f111c); font-weight: 500;
|
|
12
|
+
animation: cx-toastSlide .18s ease;
|
|
13
|
+
cursor: pointer;
|
|
14
|
+
}
|
|
15
|
+
.toast.ok { border-left: 3px solid var(--success, #27ae60); }
|
|
16
|
+
.toast.err { border-left: 3px solid var(--danger, #f64747); }
|
|
17
|
+
@keyframes cx-toastSlide { from { opacity: 0; transform: translateX(16px); } to { opacity: 1; transform: translateX(0); } }
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
let _id = 'cynx-toast-styles';
|
|
21
|
+
function ensureStyles() {
|
|
22
|
+
if (typeof document === 'undefined') return;
|
|
23
|
+
if (document.getElementById(_id)) return;
|
|
24
|
+
const tag = document.createElement('style');
|
|
25
|
+
tag.id = _id;
|
|
26
|
+
tag.textContent = STYLES;
|
|
27
|
+
document.head.appendChild(tag);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ── Module-level state ────────────────────────────────────────────────────────
|
|
31
|
+
let _toasts = [];
|
|
32
|
+
let _listeners = new Set();
|
|
33
|
+
|
|
34
|
+
function notify() {
|
|
35
|
+
const snapshot = [..._toasts];
|
|
36
|
+
_listeners.forEach(fn => fn(snapshot));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Global toast — call from anywhere, no hook needed
|
|
40
|
+
export function toast(msg, type = 'ok') {
|
|
41
|
+
ensureStyles();
|
|
42
|
+
const id = Date.now() + Math.random();
|
|
43
|
+
_toasts = [..._toasts, { id, msg, type }];
|
|
44
|
+
notify();
|
|
45
|
+
setTimeout(() => {
|
|
46
|
+
_toasts = _toasts.filter(t => t.id !== id);
|
|
47
|
+
notify();
|
|
48
|
+
}, 3500);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ── Hook — for components that want reactive state ────────────────────────────
|
|
52
|
+
export function useToast() {
|
|
53
|
+
const [toasts, setToasts] = useState(_toasts);
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
_listeners.add(setToasts);
|
|
57
|
+
return () => _listeners.delete(setToasts);
|
|
58
|
+
}, []);
|
|
59
|
+
|
|
60
|
+
const remove = useCallback(id => {
|
|
61
|
+
_toasts = _toasts.filter(t => t.id !== id);
|
|
62
|
+
notify();
|
|
63
|
+
}, []);
|
|
64
|
+
|
|
65
|
+
return { toasts, toast, remove };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ── Render component ──────────────────────────────────────────────────────────
|
|
69
|
+
export function Toast({ toasts, remove }) {
|
|
70
|
+
ensureStyles();
|
|
71
|
+
return createPortal(
|
|
72
|
+
<div id="toast-root">
|
|
73
|
+
{toasts.map(t => (
|
|
74
|
+
<div key={t.id} className={`toast ${t.type}`} onClick={() => remove(t.id)}>
|
|
75
|
+
{t.type === 'ok'
|
|
76
|
+
? <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--success)" strokeWidth="2.5" strokeLinecap="round"><polyline points="20 6 9 17 4 12"/></svg>
|
|
77
|
+
: <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--danger)" strokeWidth="2.5" strokeLinecap="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>
|
|
78
|
+
}
|
|
79
|
+
{t.msg}
|
|
80
|
+
</div>
|
|
81
|
+
))}
|
|
82
|
+
</div>,
|
|
83
|
+
document.body
|
|
84
|
+
);
|
|
85
|
+
}
|