cynx-lib 1.3.66 → 1.3.67

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cynx-lib",
3
- "version": "1.3.66",
3
+ "version": "1.3.67",
4
4
  "type": "module",
5
5
  "main": "./index.cjs",
6
6
  "module": "./index.js",
@@ -0,0 +1,208 @@
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { createPortal } from 'react-dom';
3
+ import { X } from 'lucide-react';
4
+
5
+ const DRAWER_STYLES = `
6
+ @keyframes cynxDrawerFadeIn { from { opacity: 0; } to { opacity: 1; } }
7
+ @keyframes cynxDrawerFadeOut { from { opacity: 1; } to { opacity: 0; } }
8
+ @keyframes cynxDrawerSlideInRight { from { transform: translateX(100%); } to { transform: translateX(0); } }
9
+ @keyframes cynxDrawerSlideOutRight { from { transform: translateX(0); } to { transform: translateX(100%); } }
10
+ @keyframes cynxDrawerSlideInLeft { from { transform: translateX(-100%); } to { transform: translateX(0); } }
11
+ @keyframes cynxDrawerSlideOutLeft { from { transform: translateX(0); } to { transform: translateX(-100%); } }
12
+ `;
13
+
14
+ let _drawer_id = 'cynx-drawer-styles';
15
+ function ensureDrawerStyles() {
16
+ if (typeof document === 'undefined') return;
17
+ if (document.getElementById(_drawer_id)) return;
18
+ const tag = document.createElement('style');
19
+ tag.id = _drawer_id;
20
+ tag.textContent = DRAWER_STYLES;
21
+ document.head.appendChild(tag);
22
+ }
23
+
24
+ export function Drawer({
25
+ open = false,
26
+ onClose,
27
+ title = '',
28
+ description = '',
29
+ width = 560,
30
+ side = 'right',
31
+ showClose = true,
32
+ children,
33
+ footer,
34
+ header,
35
+ style = {},
36
+ bodyStyle = {},
37
+ }) {
38
+ const [closing, setClosing] = useState(false);
39
+
40
+ useEffect(() => {
41
+ ensureDrawerStyles();
42
+ }, []);
43
+
44
+ const handleClose = () => {
45
+ if (closing) return;
46
+ setClosing(true);
47
+ setTimeout(() => {
48
+ setClosing(false);
49
+ onClose?.();
50
+ }, 260);
51
+ };
52
+
53
+ useEffect(() => {
54
+ if (typeof document === 'undefined') return;
55
+ const handleKeydown = (e) => {
56
+ if (e.key === 'Escape' && open) handleClose();
57
+ };
58
+ if (open) {
59
+ document.body.style.overflow = 'hidden';
60
+ window.addEventListener('keydown', handleKeydown);
61
+ } else {
62
+ document.body.style.overflow = '';
63
+ window.removeEventListener('keydown', handleKeydown);
64
+ }
65
+ return () => {
66
+ document.body.style.overflow = '';
67
+ window.removeEventListener('keydown', handleKeydown);
68
+ };
69
+ }, [open]);
70
+
71
+ if (!open && !closing) return null;
72
+ if (typeof document === 'undefined') return null;
73
+
74
+ const resolvedWidth = typeof width === 'number' ? `${width}px` : width;
75
+ const slideInAnim = side === 'left' ? 'cynxDrawerSlideInLeft' : 'cynxDrawerSlideInRight';
76
+ const slideOutAnim = side === 'left' ? 'cynxDrawerSlideOutLeft' : 'cynxDrawerSlideOutRight';
77
+
78
+ return createPortal(
79
+ <div
80
+ style={{
81
+ position: 'fixed',
82
+ inset: 0,
83
+ background: 'rgba(20, 22, 41, .45)',
84
+ zIndex: 8500,
85
+ display: 'flex',
86
+ justifyContent: side === 'left' ? 'flex-start' : 'flex-end',
87
+ animation: `${closing ? 'cynxDrawerFadeOut' : 'cynxDrawerFadeIn'} .26s ease forwards`,
88
+ }}
89
+ onMouseDown={(e) => {
90
+ if (e.target === e.currentTarget) handleClose();
91
+ }}
92
+ >
93
+ <div
94
+ style={{
95
+ background: 'var(--surface, #ffffff)',
96
+ borderLeft: side === 'right' ? '1px solid var(--border, var(--bd, #e2e8f0))' : 'none',
97
+ borderRight: side === 'left' ? '1px solid var(--border, var(--bd, #e2e8f0))' : 'none',
98
+ width: resolvedWidth,
99
+ maxWidth: '100vw',
100
+ height: '100%',
101
+ boxShadow: 'var(--shadow-lg, 0 20px 60px rgba(0, 0, 0, .25))',
102
+ display: 'flex',
103
+ flexDirection: 'column',
104
+ overflow: 'hidden',
105
+ animation: `${closing ? slideOutAnim : slideInAnim} .26s cubic-bezier(.16, 1, .3, 1) forwards`,
106
+ ...style,
107
+ }}
108
+ onMouseDown={(e) => e.stopPropagation()}
109
+ >
110
+ {/* Header */}
111
+ <div
112
+ style={{
113
+ display: 'flex',
114
+ alignItems: 'center',
115
+ justifyContent: 'space-between',
116
+ padding: '16px 20px',
117
+ borderBottom: '1px solid var(--border-light, #ebebf0)',
118
+ flexShrink: 0,
119
+ }}
120
+ >
121
+ {header ? (
122
+ header
123
+ ) : (
124
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
125
+ <span
126
+ style={{
127
+ fontSize: '1rem',
128
+ fontWeight: 700,
129
+ letterSpacing: '-.01em',
130
+ color: 'var(--t, var(--primary-text, #0f111c))',
131
+ }}
132
+ >
133
+ {{ title }}
134
+ </span>
135
+ {description && (
136
+ <span
137
+ style={{
138
+ fontSize: '.75rem',
139
+ color: 'var(--muted, #797b8d)',
140
+ }}
141
+ >
142
+ {description}
143
+ </span>
144
+ )}
145
+ </div>
146
+ )}
147
+
148
+ {showClose && (
149
+ <button
150
+ type="button"
151
+ style={{
152
+ background: 'none',
153
+ border: 'none',
154
+ cursor: 'pointer',
155
+ padding: '6px',
156
+ borderRadius: '6px',
157
+ color: 'var(--muted, #797b8d)',
158
+ display: 'flex',
159
+ alignItems: 'center',
160
+ justifyContent: 'center',
161
+ transition: 'all .14s',
162
+ }}
163
+ onClick={handleClose}
164
+ >
165
+ <X size={18} />
166
+ </button>
167
+ )}
168
+ </div>
169
+
170
+ {/* Body */}
171
+ <div
172
+ className="custom-scrollbar"
173
+ style={{
174
+ padding: '20px',
175
+ overflowX: 'hidden',
176
+ overflowY: 'auto',
177
+ flex: '1 1 auto',
178
+ minHeight: 0,
179
+ color: 'var(--t, var(--primary-text, #0f111c))',
180
+ ...bodyStyle,
181
+ }}
182
+ >
183
+ {children}
184
+ </div>
185
+
186
+ {/* Footer */}
187
+ {footer && (
188
+ <div
189
+ style={{
190
+ display: 'flex',
191
+ justifyContent: 'flex-end',
192
+ gap: 8,
193
+ padding: '14px 20px',
194
+ borderTop: '1px solid var(--border-light, #ebebf0)',
195
+ background: 'var(--grey-1, #f5f5f8)',
196
+ flexShrink: 0,
197
+ }}
198
+ >
199
+ {footer}
200
+ </div>
201
+ )}
202
+ </div>
203
+ </div>,
204
+ document.body
205
+ );
206
+ }
207
+
208
+ export default Drawer;
package/ui/index.cjs CHANGED
@@ -41,6 +41,7 @@ __export(index_exports, {
41
41
  ConfirmRoot: () => ConfirmRoot,
42
42
  DatePicker: () => DatePicker,
43
43
  Divider: () => Divider,
44
+ Drawer: () => Drawer,
44
45
  Dropzone: () => Dropzone,
45
46
  FilePicker: () => FilePicker,
46
47
  HoverCard: () => HoverCard,
@@ -111,6 +112,7 @@ var import_PulseDot = __toESM(require("./components/PulseDot.jsx"), 1);
111
112
  var import_CircularProgress = __toESM(require("./components/CircularProgress.jsx"), 1);
112
113
  var import_Skeleton = __toESM(require("./components/Skeleton.jsx"), 1);
113
114
  var import_Modal = require("./components/Modal.jsx");
115
+ var import_Drawer = require("./components/Drawer.jsx");
114
116
  var import_Toast = require("./components/Toast.jsx");
115
117
  var import_Confirm = require("./components/Confirm.jsx");
116
118
  var import_Pagination = require("./components/Pagination.jsx");
@@ -157,6 +159,7 @@ const PulseDot = import_PulseDot.default;
157
159
  const CircularProgress = import_CircularProgress.default;
158
160
  const Skeleton = import_Skeleton.default;
159
161
  const Modal = import_Modal.Modal;
162
+ const Drawer = import_Drawer.Drawer;
160
163
  const Confirm = import_Modal.Confirm;
161
164
  const toast = import_Toast.toast;
162
165
  const useToast = import_Toast.useToast;
package/ui/index.js CHANGED
@@ -21,6 +21,7 @@ import { default as _PulseDot } from './components/PulseDot.jsx';
21
21
  import { default as _CircularProgress } from './components/CircularProgress.jsx';
22
22
  import { default as _Skeleton } from './components/Skeleton.jsx';
23
23
  import { Modal as _Modal, Confirm as _Confirm } from './components/Modal.jsx';
24
+ import { Drawer as _Drawer } from './components/Drawer.jsx';
24
25
  import { toast as _toast, useToast as _useToast, Toast as _Toast } from './components/Toast.jsx';
25
26
  import { ConfirmRoot as _ConfirmRoot, confirm as _confirm } from './components/Confirm.jsx';
26
27
  import { Pagination as _Pagination } from './components/Pagination.jsx';
@@ -69,6 +70,7 @@ export const PulseDot = _PulseDot;
69
70
  export const CircularProgress = _CircularProgress;
70
71
  export const Skeleton = _Skeleton;
71
72
  export const Modal = _Modal;
73
+ export const Drawer = _Drawer;
72
74
  export const Confirm = _Confirm;
73
75
  export const toast = _toast;
74
76
  export const useToast = _useToast;
@@ -0,0 +1,243 @@
1
+ <script setup lang="ts">
2
+ import { ref, watch, onUnmounted, computed } from "vue";
3
+ import { X } from "lucide-vue-next";
4
+
5
+ const props = withDefaults(
6
+ defineProps<{
7
+ modelValue?: boolean;
8
+ open?: boolean;
9
+ title?: string;
10
+ description?: string;
11
+ width?: number | string;
12
+ maxWidth?: number | string;
13
+ side?: "right" | "left";
14
+ showClose?: boolean;
15
+ bodyStyle?: Record<string, any>;
16
+ style?: Record<string, any>;
17
+ }>(),
18
+ {
19
+ modelValue: false,
20
+ open: false,
21
+ title: "",
22
+ description: "",
23
+ width: 560,
24
+ maxWidth: "100vw",
25
+ side: "right",
26
+ showClose: true,
27
+ bodyStyle: () => ({}),
28
+ style: () => ({}),
29
+ }
30
+ );
31
+
32
+ const emit = defineEmits<{
33
+ (e: "update:modelValue", value: boolean): void;
34
+ (e: "update:open", value: boolean): void;
35
+ (e: "close"): void;
36
+ }>();
37
+
38
+ const isOpen = computed(() => props.modelValue || props.open);
39
+ const closing = ref(false);
40
+
41
+ const STYLES = `
42
+ @keyframes cynxDrawerFadeIn { from { opacity: 0; } to { opacity: 1; } }
43
+ @keyframes cynxDrawerFadeOut { from { opacity: 1; } to { opacity: 0; } }
44
+ @keyframes cynxDrawerSlideInRight { from { transform: translateX(100%); } to { transform: translateX(0); } }
45
+ @keyframes cynxDrawerSlideOutRight { from { transform: translateX(0); } to { transform: translateX(100%); } }
46
+ @keyframes cynxDrawerSlideInLeft { from { transform: translateX(-100%); } to { transform: translateX(0); } }
47
+ @keyframes cynxDrawerSlideOutLeft { from { transform: translateX(0); } to { transform: translateX(-100%); } }
48
+ `;
49
+
50
+ let _id = "cynx-drawer-styles";
51
+ function ensureStyles() {
52
+ if (typeof document === "undefined") return;
53
+ if (document.getElementById(_id)) return;
54
+ const tag = document.createElement("style");
55
+ tag.id = _id;
56
+ tag.textContent = STYLES;
57
+ document.head.appendChild(tag);
58
+ }
59
+
60
+ if (typeof document !== "undefined") ensureStyles();
61
+
62
+ function close() {
63
+ if (closing.value) return;
64
+ closing.value = true;
65
+ setTimeout(() => {
66
+ closing.value = false;
67
+ emit("update:modelValue", false);
68
+ emit("update:open", false);
69
+ emit("close");
70
+ }, 260);
71
+ }
72
+
73
+ function handleKeydown(e: KeyboardEvent) {
74
+ if (e.key === "Escape" && isOpen.value) {
75
+ close();
76
+ }
77
+ }
78
+
79
+ watch(
80
+ isOpen,
81
+ (val) => {
82
+ if (typeof document === "undefined") return;
83
+ if (val) {
84
+ document.body.style.overflow = "hidden";
85
+ window.addEventListener("keydown", handleKeydown);
86
+ } else {
87
+ document.body.style.overflow = "";
88
+ window.removeEventListener("keydown", handleKeydown);
89
+ }
90
+ },
91
+ { immediate: true }
92
+ );
93
+
94
+ onUnmounted(() => {
95
+ if (typeof document !== "undefined") {
96
+ document.body.style.overflow = "";
97
+ window.removeEventListener("keydown", handleKeydown);
98
+ }
99
+ });
100
+
101
+ const resolvedWidth = computed(() => {
102
+ if (typeof props.width === "number") return `${props.width}px`;
103
+ return props.width;
104
+ });
105
+
106
+ const slideInAnim = computed(() =>
107
+ props.side === "left" ? "cynxDrawerSlideInLeft" : "cynxDrawerSlideInRight"
108
+ );
109
+ const slideOutAnim = computed(() =>
110
+ props.side === "left" ? "cynxDrawerSlideOutLeft" : "cynxDrawerSlideOutRight"
111
+ );
112
+ </script>
113
+
114
+ <template>
115
+ <Teleport to="body">
116
+ <div
117
+ v-if="isOpen || closing"
118
+ class="cynx-drawer-backdrop"
119
+ :style="{
120
+ position: 'fixed',
121
+ inset: 0,
122
+ background: 'rgba(20, 22, 41, .45)',
123
+ zIndex: 8500,
124
+ display: 'flex',
125
+ justifyContent: side === 'left' ? 'flex-start' : 'flex-end',
126
+ animation: `${closing ? 'cynxDrawerFadeOut' : 'cynxDrawerFadeIn'} .26s ease forwards`,
127
+ }"
128
+ @mousedown.self="close"
129
+ >
130
+ <div
131
+ class="cynx-drawer-panel"
132
+ :style="{
133
+ background: 'var(--surface, #ffffff)',
134
+ borderLeft: side === 'right' ? '1px solid var(--border, var(--bd, #e2e8f0))' : 'none',
135
+ borderRight: side === 'left' ? '1px solid var(--border, var(--bd, #e2e8f0))' : 'none',
136
+ width: resolvedWidth,
137
+ maxWidth: '100vw',
138
+ height: '100%',
139
+ boxShadow: 'var(--shadow-lg, 0 20px 60px rgba(0, 0, 0, .25))',
140
+ display: 'flex',
141
+ flexDirection: 'column',
142
+ overflow: 'hidden',
143
+ animation: `${closing ? slideOutAnim : slideInAnim} .26s cubic-bezier(.16, 1, .3, 1) forwards`,
144
+ ...style,
145
+ }"
146
+ @mousedown.stop
147
+ >
148
+ <!-- Header -->
149
+ <div
150
+ class="cynx-drawer-hdr"
151
+ :style="{
152
+ display: 'flex',
153
+ alignItems: 'center',
154
+ justifyContent: 'space-between',
155
+ padding: '16px 20px',
156
+ borderBottom: '1px solid var(--border-light, #ebebf0)',
157
+ flexShrink: 0,
158
+ }"
159
+ >
160
+ <slot name="header">
161
+ <div style="display: flex; flex-direction: column; gap: 2px;">
162
+ <span
163
+ class="cynx-drawer-title"
164
+ :style="{
165
+ fontSize: '1rem',
166
+ fontWeight: 700,
167
+ letterSpacing: '-.01em',
168
+ color: 'var(--t, var(--primary-text, #0f111c))',
169
+ }"
170
+ >
171
+ {{ title }}
172
+ </span>
173
+ <span
174
+ v-if="description"
175
+ class="cynx-drawer-desc"
176
+ :style="{
177
+ fontSize: '.75rem',
178
+ color: 'var(--muted, #797b8d)',
179
+ }"
180
+ >
181
+ {{ description }}
182
+ </span>
183
+ </div>
184
+ </slot>
185
+
186
+ <button
187
+ v-if="showClose"
188
+ type="button"
189
+ class="cynx-drawer-close"
190
+ :style="{
191
+ background: 'none',
192
+ border: 'none',
193
+ cursor: 'pointer',
194
+ padding: '6px',
195
+ borderRadius: '6px',
196
+ color: 'var(--muted, #797b8d)',
197
+ display: 'flex',
198
+ alignItems: 'center',
199
+ justifyContent: 'center',
200
+ transition: 'all .14s',
201
+ }"
202
+ @click="close"
203
+ >
204
+ <X :size="18" />
205
+ </button>
206
+ </div>
207
+
208
+ <!-- Body -->
209
+ <div
210
+ class="cynx-drawer-body custom-scrollbar"
211
+ :style="{
212
+ padding: '20px',
213
+ overflowX: 'hidden',
214
+ overflowY: 'auto',
215
+ flex: '1 1 auto',
216
+ minHeight: 0,
217
+ color: 'var(--t, var(--primary-text, #0f111c))',
218
+ ...bodyStyle,
219
+ }"
220
+ >
221
+ <slot />
222
+ </div>
223
+
224
+ <!-- Footer -->
225
+ <div
226
+ v-if="$slots.footer"
227
+ class="cynx-drawer-footer"
228
+ :style="{
229
+ display: 'flex',
230
+ justifyContent: 'flex-end',
231
+ gap: '8px',
232
+ padding: '14px 20px',
233
+ borderTop: '1px solid var(--border-light, #ebebf0)',
234
+ background: 'var(--grey-1, #f5f5f8)',
235
+ flexShrink: 0,
236
+ }"
237
+ >
238
+ <slot name="footer" />
239
+ </div>
240
+ </div>
241
+ </div>
242
+ </Teleport>
243
+ </template>
@@ -64,6 +64,7 @@ const props = withDefaults(
64
64
  placeholder?: string;
65
65
  disabled?: boolean;
66
66
  multi?: boolean;
67
+ multiple?: boolean;
67
68
  searchable?: boolean;
68
69
  clearable?: boolean;
69
70
  size?: "xs" | "sm" | "md" | "lg";
@@ -82,6 +83,7 @@ const props = withDefaults(
82
83
  placeholder: "Select...",
83
84
  disabled: false,
84
85
  multi: false,
86
+ multiple: false,
85
87
  searchable: false,
86
88
  clearable: false,
87
89
  size: "md",
@@ -118,9 +120,11 @@ const currentVal = computed(() => {
118
120
  return props.modelValue !== undefined ? props.modelValue : props.value;
119
121
  });
120
122
 
123
+ const isMulti = computed(() => Boolean(props.multi || props.multiple));
124
+
121
125
  const selectedValues = computed<any[]>(() => {
122
126
  const v = currentVal.value;
123
- if (props.multi) {
127
+ if (isMulti.value) {
124
128
  return Array.isArray(v) ? v : [];
125
129
  }
126
130
  return v !== undefined && v !== null && v !== "" ? [v] : [];
@@ -133,7 +137,7 @@ function getLabel(optVal: any) {
133
137
 
134
138
  function isSelected(optVal: any) {
135
139
  const v = currentVal.value;
136
- return props.multi ? (Array.isArray(v) && v.includes(optVal)) : v === optVal;
140
+ return isMulti.value ? (Array.isArray(v) && v.includes(optVal)) : v === optVal;
137
141
  }
138
142
 
139
143
  const radiusVal = computed(() => {
@@ -279,7 +283,7 @@ function handleOpen() {
279
283
  }
280
284
 
281
285
  function toggleOption(optVal: any) {
282
- if (props.multi) {
286
+ if (isMulti.value) {
283
287
  const arr = Array.isArray(currentVal.value) ? currentVal.value : [];
284
288
  const next = arr.includes(optVal)
285
289
  ? arr.filter((v: any) => v !== optVal)
@@ -304,7 +308,7 @@ function removeTag(e: MouseEvent, valToRemove: any) {
304
308
 
305
309
  function handleClear(e: MouseEvent) {
306
310
  e.stopPropagation();
307
- const next = props.multi ? [] : "";
311
+ const next = isMulti.value ? [] : "";
308
312
  emit("update:modelValue", next);
309
313
  emit("change", next);
310
314
  emit("clear");
@@ -385,10 +389,10 @@ function handleInputKeyDown(e: KeyboardEvent) {
385
389
  <slot
386
390
  v-if="slots.label && selectedValues.length > 0"
387
391
  name="label"
388
- :value="multi ? selectedValues : selectedValues[0]"
392
+ :value="isMulti ? selectedValues : selectedValues[0]"
389
393
  />
390
394
 
391
- <template v-else-if="multi && selectedValues.length > 0">
395
+ <template v-else-if="isMulti && selectedValues.length > 0">
392
396
  <span
393
397
  v-if="showCount"
394
398
  style="
package/vue/index.js CHANGED
@@ -11,6 +11,7 @@ import Table from "./components/Table.vue";
11
11
  import Pagination from "./components/Pagination.vue";
12
12
  import Skeleton from "./components/Skeleton.vue";
13
13
  import Modal from "./components/Modal.vue";
14
+ import Drawer from "./components/Drawer.vue";
14
15
  import Toggle from "./components/Toggle.vue";
15
16
  import { XTable, XThead, XTbody, XTr, XTh, XTd } from "./components/XTable.vue";
16
17
  import { useAutoSkeletonRows } from "./composables/useAutoSkeletonRows";
@@ -28,6 +29,7 @@ export {
28
29
  Pagination,
29
30
  Skeleton,
30
31
  Modal,
32
+ Drawer,
31
33
  Toggle,
32
34
  XTable,
33
35
  XThead,
@@ -51,6 +53,7 @@ export default {
51
53
  Pagination,
52
54
  Skeleton,
53
55
  Modal,
56
+ Drawer,
54
57
  Toggle,
55
58
  XTable,
56
59
  XThead,