framepexls-ui-lib 1.5.0 → 1.7.0

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.
@@ -60,6 +60,11 @@ type AuthTopbarProps = {
60
60
  threads: ChatThread[];
61
61
  messages: ChatMessage[];
62
62
  presence?: ChatPresenceUser[];
63
+ typingUsers?: ChatPresenceUser[];
64
+ onTyping?: (payload: {
65
+ threadId: string;
66
+ typing: boolean;
67
+ }) => void;
63
68
  onSendMessage: (payload: {
64
69
  threadId: string;
65
70
  text: string;
@@ -60,6 +60,11 @@ type AuthTopbarProps = {
60
60
  threads: ChatThread[];
61
61
  messages: ChatMessage[];
62
62
  presence?: ChatPresenceUser[];
63
+ typingUsers?: ChatPresenceUser[];
64
+ onTyping?: (payload: {
65
+ threadId: string;
66
+ typing: boolean;
67
+ }) => void;
63
68
  onSendMessage: (payload: {
64
69
  threadId: string;
65
70
  text: string;
@@ -156,6 +156,8 @@ function AuthTopbar({
156
156
  threads: chat.threads,
157
157
  messages: chat.messages,
158
158
  presence: chat.presence,
159
+ typingUsers: chat.typingUsers,
160
+ onTyping: chat.onTyping,
159
161
  onSendMessage: chat.onSendMessage,
160
162
  labels: { trigger: "Chat", title: "Chat", online: "Activos" }
161
163
  }
@@ -135,6 +135,8 @@ function AuthTopbar({
135
135
  threads: chat.threads,
136
136
  messages: chat.messages,
137
137
  presence: chat.presence,
138
+ typingUsers: chat.typingUsers,
139
+ onTyping: chat.onTyping,
138
140
  onSendMessage: chat.onSendMessage,
139
141
  labels: { trigger: "Chat", title: "Chat", online: "Activos" }
140
142
  }
@@ -64,11 +64,16 @@ type ChatCenterProps = {
64
64
  text: string;
65
65
  }) => void | Promise<void>;
66
66
  presence?: ChatPresenceUser[];
67
+ typingUsers?: ChatPresenceUser[];
68
+ onTyping?: (payload: {
69
+ threadId: string;
70
+ typing: boolean;
71
+ }) => void;
67
72
  unreadCount?: number;
68
73
  drawerWidthClass?: string;
69
74
  className?: string;
70
75
  labels?: ChatCenterLabels;
71
76
  };
72
- declare function ChatCenter({ me, threads, messages, open: openProp, onOpenChange, defaultThreadId, activeThreadId: activeThreadIdProp, onThreadChange, onSendMessage, presence, unreadCount: unreadCountProp, drawerWidthClass, className, labels, }: ChatCenterProps): react_jsx_runtime.JSX.Element;
77
+ declare function ChatCenter({ me, threads, messages, open: openProp, onOpenChange, defaultThreadId, activeThreadId: activeThreadIdProp, onThreadChange, onSendMessage, presence, typingUsers, onTyping, unreadCount: unreadCountProp, drawerWidthClass, className, labels, }: ChatCenterProps): react_jsx_runtime.JSX.Element;
73
78
 
74
79
  export { type ChatCenterLabels, type ChatCenterProps, type ChatMessage, type ChatPresenceUser, type ChatThread, type ChatUser, ChatCenter as default };
@@ -64,11 +64,16 @@ type ChatCenterProps = {
64
64
  text: string;
65
65
  }) => void | Promise<void>;
66
66
  presence?: ChatPresenceUser[];
67
+ typingUsers?: ChatPresenceUser[];
68
+ onTyping?: (payload: {
69
+ threadId: string;
70
+ typing: boolean;
71
+ }) => void;
67
72
  unreadCount?: number;
68
73
  drawerWidthClass?: string;
69
74
  className?: string;
70
75
  labels?: ChatCenterLabels;
71
76
  };
72
- declare function ChatCenter({ me, threads, messages, open: openProp, onOpenChange, defaultThreadId, activeThreadId: activeThreadIdProp, onThreadChange, onSendMessage, presence, unreadCount: unreadCountProp, drawerWidthClass, className, labels, }: ChatCenterProps): react_jsx_runtime.JSX.Element;
77
+ declare function ChatCenter({ me, threads, messages, open: openProp, onOpenChange, defaultThreadId, activeThreadId: activeThreadIdProp, onThreadChange, onSendMessage, presence, typingUsers, onTyping, unreadCount: unreadCountProp, drawerWidthClass, className, labels, }: ChatCenterProps): react_jsx_runtime.JSX.Element;
73
78
 
74
79
  export { type ChatCenterLabels, type ChatCenterProps, type ChatMessage, type ChatPresenceUser, type ChatThread, type ChatUser, ChatCenter as default };
@@ -65,6 +65,8 @@ function ChatCenter({
65
65
  onThreadChange,
66
66
  onSendMessage,
67
67
  presence,
68
+ typingUsers,
69
+ onTyping,
68
70
  unreadCount: unreadCountProp,
69
71
  drawerWidthClass,
70
72
  className,
@@ -89,7 +91,11 @@ function ChatCenter({
89
91
  const [text, setText] = import_react.default.useState("");
90
92
  const [sending, setSending] = import_react.default.useState(false);
91
93
  const endRef = import_react.default.useRef(null);
94
+ const messagesScrollRef = import_react.default.useRef(null);
92
95
  const composerRef = import_react.default.useRef(null);
96
+ const typingTimeoutRef = import_react.default.useRef(null);
97
+ const typingActiveRef = import_react.default.useRef(false);
98
+ const typingLastEmitAtRef = import_react.default.useRef(0);
93
99
  const labelTrigger = (_a = labels == null ? void 0 : labels.trigger) != null ? _a : "Chat";
94
100
  const labelTitle = (_b = labels == null ? void 0 : labels.title) != null ? _b : "Chat";
95
101
  const labelSearchThreads = (_c = labels == null ? void 0 : labels.searchThreads) != null ? _c : "Buscar chats\u2026";
@@ -142,22 +148,107 @@ function ChatCenter({
142
148
  return (_a2 = composerRef.current) == null ? void 0 : _a2.focus();
143
149
  }, 0);
144
150
  }, [open, activeThreadId]);
151
+ import_react.default.useEffect(() => {
152
+ if (typingTimeoutRef.current) window.clearTimeout(typingTimeoutRef.current);
153
+ typingTimeoutRef.current = null;
154
+ if (typingActiveRef.current && activeThreadId) {
155
+ try {
156
+ onTyping == null ? void 0 : onTyping({ threadId: activeThreadId, typing: false });
157
+ } catch {
158
+ }
159
+ }
160
+ typingActiveRef.current = false;
161
+ }, [activeThreadId, open]);
145
162
  import_react.default.useEffect(() => {
146
163
  var _a2;
147
164
  if (!open) return;
148
- (_a2 = endRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth", block: "end" });
165
+ const el = messagesScrollRef.current;
166
+ if (!el) {
167
+ (_a2 = endRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "auto", block: "end" });
168
+ return;
169
+ }
170
+ const raf1 = requestAnimationFrame(() => {
171
+ const raf2 = requestAnimationFrame(() => {
172
+ var _a3;
173
+ try {
174
+ el.scrollTop = el.scrollHeight;
175
+ } catch {
176
+ (_a3 = endRef.current) == null ? void 0 : _a3.scrollIntoView({ behavior: "auto", block: "end" });
177
+ }
178
+ });
179
+ void raf2;
180
+ });
181
+ return () => cancelAnimationFrame(raf1);
149
182
  }, [open, activeThreadId, filteredMessages.length]);
183
+ const setTextWithTyping = (next) => {
184
+ setText(next);
185
+ if (!activeThreadId) return;
186
+ if (!onTyping) return;
187
+ const shouldTyping = Boolean(next.trim().length);
188
+ const emitTypingTrue = () => {
189
+ typingLastEmitAtRef.current = Date.now();
190
+ try {
191
+ onTyping({ threadId: activeThreadId, typing: true });
192
+ } catch {
193
+ }
194
+ };
195
+ if (shouldTyping) {
196
+ if (!typingActiveRef.current) {
197
+ typingActiveRef.current = true;
198
+ emitTypingTrue();
199
+ } else {
200
+ const now = Date.now();
201
+ if (now - typingLastEmitAtRef.current > 900) {
202
+ emitTypingTrue();
203
+ }
204
+ }
205
+ }
206
+ if (typingTimeoutRef.current) window.clearTimeout(typingTimeoutRef.current);
207
+ typingTimeoutRef.current = window.setTimeout(() => {
208
+ typingTimeoutRef.current = null;
209
+ if (!typingActiveRef.current) return;
210
+ typingActiveRef.current = false;
211
+ typingLastEmitAtRef.current = 0;
212
+ try {
213
+ onTyping({ threadId: activeThreadId, typing: false });
214
+ } catch {
215
+ }
216
+ }, 1800);
217
+ if (!shouldTyping && typingActiveRef.current) {
218
+ typingActiveRef.current = false;
219
+ typingLastEmitAtRef.current = 0;
220
+ try {
221
+ onTyping({ threadId: activeThreadId, typing: false });
222
+ } catch {
223
+ }
224
+ }
225
+ };
150
226
  const send = async () => {
151
227
  const t = activeThreadId;
152
228
  const msg = text.trim();
153
229
  if (!t || !msg) return;
154
230
  try {
155
231
  setSending(true);
232
+ if (typingTimeoutRef.current) window.clearTimeout(typingTimeoutRef.current);
233
+ typingTimeoutRef.current = null;
234
+ if (typingActiveRef.current) {
235
+ typingActiveRef.current = false;
236
+ try {
237
+ onTyping == null ? void 0 : onTyping({ threadId: t, typing: false });
238
+ } catch {
239
+ }
240
+ }
156
241
  await onSendMessage({ threadId: t, text: msg });
157
242
  setText("");
158
243
  window.setTimeout(() => {
159
- var _a2;
160
- return (_a2 = endRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth", block: "end" });
244
+ var _a2, _b2;
245
+ try {
246
+ const el = messagesScrollRef.current;
247
+ if (el) el.scrollTop = el.scrollHeight;
248
+ else (_a2 = endRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "auto", block: "end" });
249
+ } catch {
250
+ (_b2 = endRef.current) == null ? void 0 : _b2.scrollIntoView({ behavior: "auto", block: "end" });
251
+ }
161
252
  }, 0);
162
253
  } finally {
163
254
  setSending(false);
@@ -293,7 +384,7 @@ function ChatCenter({
293
384
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "md:hidden", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Tooltip.default, { content: labelBack, placement: "left", offset: 10, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ActionIconButton.default, { title: labelBack, size: "sm", onClick: () => setMobileTab("threads"), className: "border border-[var(--border)] bg-[var(--card)] shadow-sm", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.ArrowLeftIcon, { "aria-hidden": true }) }) }) })
294
385
  ] })
295
386
  ] }) }),
296
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "min-h-0 flex-1 overflow-y-auto px-4 py-4", children: filteredMessages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "grid place-items-center rounded-3xl border border-[var(--border)] bg-[color-mix(in_oklab,var(--card)_92%,transparent)] p-10 text-center", children: [
387
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: messagesScrollRef, className: "min-h-0 flex-1 overflow-y-auto px-4 py-4", children: filteredMessages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "grid place-items-center rounded-3xl border border-[var(--border)] bg-[color-mix(in_oklab,var(--card)_92%,transparent)] p-10 text-center", children: [
297
388
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-sm font-semibold text-[var(--foreground)]", children: emptyMessagesTitle }),
298
389
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mt-1 text-xs text-[var(--muted)]", children: emptyMessagesSubtitle })
299
390
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-2", children: [
@@ -336,36 +427,42 @@ function ChatCenter({
336
427
  }),
337
428
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: endRef })
338
429
  ] }) }),
339
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "border-t border-[var(--border)] bg-[var(--card)] px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-end gap-2", children: [
340
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
341
- import_Input.default,
342
- {
343
- ref: composerRef,
344
- value: text,
345
- onChange: (e) => setText(e.currentTarget.value),
346
- placeholder: composerPlaceholder,
347
- className: "flex-1",
348
- onKeyDown: (e) => {
349
- if (e.key === "Enter" && !e.shiftKey) {
350
- e.preventDefault();
351
- void send();
430
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "border-t border-[var(--border)] bg-[var(--card)] px-4 py-3", children: [
431
+ typingUsers && typingUsers.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mb-2 flex items-center gap-2 px-1 text-[0.75rem] text-[var(--muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "inline-flex items-center gap-1", children: [
432
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "h-1.5 w-1.5 rounded-full bg-[var(--primary)] opacity-80", "aria-hidden": true }),
433
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "truncate", children: typingUsers.length === 1 ? `${typingUsers[0].name} est\xE1 escribiendo\u2026` : `${typingUsers.slice(0, 2).map((u) => u.name).join(", ")}${typingUsers.length > 2 ? ` y ${typingUsers.length - 2} m\xE1s` : ""} est\xE1n escribiendo\u2026` })
434
+ ] }) }) : null,
435
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-end gap-2", children: [
436
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
437
+ import_Input.default,
438
+ {
439
+ ref: composerRef,
440
+ value: text,
441
+ onChange: (e) => setTextWithTyping(e.currentTarget.value),
442
+ placeholder: composerPlaceholder,
443
+ className: "flex-1",
444
+ onKeyDown: (e) => {
445
+ if (e.key === "Enter" && !e.shiftKey) {
446
+ e.preventDefault();
447
+ void send();
448
+ }
352
449
  }
353
450
  }
354
- }
355
- ),
356
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
357
- import_Button.default,
358
- {
359
- size: "sm",
360
- variant: "primary",
361
- disabled: !activeThreadId || !text.trim() || sending,
362
- loading: sending,
363
- onClick: () => void send(),
364
- leftIcon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.PaperPlaneIcon, { "aria-hidden": true, className: "h-4 w-4" }),
365
- children: labelSend
366
- }
367
- )
368
- ] }) })
451
+ ),
452
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
453
+ import_Button.default,
454
+ {
455
+ size: "sm",
456
+ variant: "primary",
457
+ disabled: !activeThreadId || !text.trim() || sending,
458
+ loading: sending,
459
+ onClick: () => void send(),
460
+ leftIcon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.PaperPlaneIcon, { "aria-hidden": true, className: "h-4 w-4" }),
461
+ children: labelSend
462
+ }
463
+ )
464
+ ] })
465
+ ] })
369
466
  ] });
370
467
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
371
468
  Trigger,
@@ -394,32 +491,25 @@ function ChatCenter({
394
491
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ActionIconButton.default, { title: "Cerrar", size: "md", onClick: () => setOpen(false), className: "border border-[var(--border)] bg-[var(--card)] shadow-sm", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.CloseIcon, { "aria-hidden": true, className: "h-4.5 w-4.5" }) })
395
492
  ] })
396
493
  ] }) }),
397
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Drawer.default.Body, { className: "bg-[linear-gradient(180deg,color-mix(in_oklab,var(--surface)_65%,transparent),color-mix(in_oklab,var(--bg)_88%,transparent))]", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-[calc(100vh-10rem)] min-h-[520px]", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "grid h-full grid-cols-1 md:grid-cols-[360px_1fr]", children: [
494
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Drawer.default.Body, { className: "overflow-hidden bg-[linear-gradient(180deg,color-mix(in_oklab,var(--surface)_65%,transparent),color-mix(in_oklab,var(--bg)_88%,transparent))]", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-full min-h-0", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "grid h-full min-h-0 grid-cols-1 md:grid-cols-[360px_1fr]", children: [
398
495
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cx("h-full border-r border-[var(--border)] bg-[color-mix(in_oklab,var(--card)_90%,transparent)]", mobileTab === "threads" ? "" : "hidden md:block"), children: ThreadsPane }),
399
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cx("h-full", mobileTab === "chat" ? "" : "hidden md:block"), children: activeThread ? MessagesPane : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "grid h-full place-items-center p-8", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rounded-3xl border border-[var(--border)] bg-[color-mix(in_oklab,var(--card)_92%,transparent)] p-10 text-center", children: [
496
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cx("h-full min-h-0", mobileTab === "chat" ? "" : "hidden md:block"), children: activeThread ? MessagesPane : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "grid h-full place-items-center p-8", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rounded-3xl border border-[var(--border)] bg-[color-mix(in_oklab,var(--card)_92%,transparent)] p-10 text-center", children: [
400
497
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-sm font-semibold text-[var(--foreground)]", children: "Selecciona un chat" }),
401
498
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mt-1 text-xs text-[var(--muted)]", children: "Elige una conversaci\xF3n para ver mensajes." })
402
499
  ] }) }) })
403
500
  ] }) }) }),
404
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Drawer.default.Footer, { sticky: true, className: "bg-[color-mix(in_oklab,var(--card)_90%,transparent)]", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between gap-3", children: [
405
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "text-xs text-[var(--muted)]", children: [
406
- "Conectado como ",
407
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "font-semibold text-[var(--foreground)]", children: me.name })
408
- ] }),
409
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "md:hidden", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
410
- import_SegmentedTabs.default,
411
- {
412
- value: mobileTab,
413
- size: "sm",
414
- onChange: (v) => setMobileTab(v),
415
- options: [
416
- { value: "threads", label: "Chats" },
417
- { value: "chat", label: "Mensaje" }
418
- ]
419
- }
420
- ) }),
421
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hidden md:block", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Button.default, { variant: "outline", size: "sm", onClick: () => setOpen(false), children: "Cerrar" }) })
422
- ] }) })
501
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Drawer.default.Footer, { sticky: true, className: "bg-[color-mix(in_oklab,var(--card)_90%,transparent)] md:hidden", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
502
+ import_SegmentedTabs.default,
503
+ {
504
+ value: mobileTab,
505
+ size: "sm",
506
+ onChange: (v) => setMobileTab(v),
507
+ options: [
508
+ { value: "threads", label: "Chats" },
509
+ { value: "chat", label: "Mensaje" }
510
+ ]
511
+ }
512
+ ) })
423
513
  ] })
424
514
  ] });
425
515
  }
@@ -32,6 +32,8 @@ function ChatCenter({
32
32
  onThreadChange,
33
33
  onSendMessage,
34
34
  presence,
35
+ typingUsers,
36
+ onTyping,
35
37
  unreadCount: unreadCountProp,
36
38
  drawerWidthClass,
37
39
  className,
@@ -56,7 +58,11 @@ function ChatCenter({
56
58
  const [text, setText] = React.useState("");
57
59
  const [sending, setSending] = React.useState(false);
58
60
  const endRef = React.useRef(null);
61
+ const messagesScrollRef = React.useRef(null);
59
62
  const composerRef = React.useRef(null);
63
+ const typingTimeoutRef = React.useRef(null);
64
+ const typingActiveRef = React.useRef(false);
65
+ const typingLastEmitAtRef = React.useRef(0);
60
66
  const labelTrigger = (_a = labels == null ? void 0 : labels.trigger) != null ? _a : "Chat";
61
67
  const labelTitle = (_b = labels == null ? void 0 : labels.title) != null ? _b : "Chat";
62
68
  const labelSearchThreads = (_c = labels == null ? void 0 : labels.searchThreads) != null ? _c : "Buscar chats\u2026";
@@ -109,22 +115,107 @@ function ChatCenter({
109
115
  return (_a2 = composerRef.current) == null ? void 0 : _a2.focus();
110
116
  }, 0);
111
117
  }, [open, activeThreadId]);
118
+ React.useEffect(() => {
119
+ if (typingTimeoutRef.current) window.clearTimeout(typingTimeoutRef.current);
120
+ typingTimeoutRef.current = null;
121
+ if (typingActiveRef.current && activeThreadId) {
122
+ try {
123
+ onTyping == null ? void 0 : onTyping({ threadId: activeThreadId, typing: false });
124
+ } catch {
125
+ }
126
+ }
127
+ typingActiveRef.current = false;
128
+ }, [activeThreadId, open]);
112
129
  React.useEffect(() => {
113
130
  var _a2;
114
131
  if (!open) return;
115
- (_a2 = endRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth", block: "end" });
132
+ const el = messagesScrollRef.current;
133
+ if (!el) {
134
+ (_a2 = endRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "auto", block: "end" });
135
+ return;
136
+ }
137
+ const raf1 = requestAnimationFrame(() => {
138
+ const raf2 = requestAnimationFrame(() => {
139
+ var _a3;
140
+ try {
141
+ el.scrollTop = el.scrollHeight;
142
+ } catch {
143
+ (_a3 = endRef.current) == null ? void 0 : _a3.scrollIntoView({ behavior: "auto", block: "end" });
144
+ }
145
+ });
146
+ void raf2;
147
+ });
148
+ return () => cancelAnimationFrame(raf1);
116
149
  }, [open, activeThreadId, filteredMessages.length]);
150
+ const setTextWithTyping = (next) => {
151
+ setText(next);
152
+ if (!activeThreadId) return;
153
+ if (!onTyping) return;
154
+ const shouldTyping = Boolean(next.trim().length);
155
+ const emitTypingTrue = () => {
156
+ typingLastEmitAtRef.current = Date.now();
157
+ try {
158
+ onTyping({ threadId: activeThreadId, typing: true });
159
+ } catch {
160
+ }
161
+ };
162
+ if (shouldTyping) {
163
+ if (!typingActiveRef.current) {
164
+ typingActiveRef.current = true;
165
+ emitTypingTrue();
166
+ } else {
167
+ const now = Date.now();
168
+ if (now - typingLastEmitAtRef.current > 900) {
169
+ emitTypingTrue();
170
+ }
171
+ }
172
+ }
173
+ if (typingTimeoutRef.current) window.clearTimeout(typingTimeoutRef.current);
174
+ typingTimeoutRef.current = window.setTimeout(() => {
175
+ typingTimeoutRef.current = null;
176
+ if (!typingActiveRef.current) return;
177
+ typingActiveRef.current = false;
178
+ typingLastEmitAtRef.current = 0;
179
+ try {
180
+ onTyping({ threadId: activeThreadId, typing: false });
181
+ } catch {
182
+ }
183
+ }, 1800);
184
+ if (!shouldTyping && typingActiveRef.current) {
185
+ typingActiveRef.current = false;
186
+ typingLastEmitAtRef.current = 0;
187
+ try {
188
+ onTyping({ threadId: activeThreadId, typing: false });
189
+ } catch {
190
+ }
191
+ }
192
+ };
117
193
  const send = async () => {
118
194
  const t = activeThreadId;
119
195
  const msg = text.trim();
120
196
  if (!t || !msg) return;
121
197
  try {
122
198
  setSending(true);
199
+ if (typingTimeoutRef.current) window.clearTimeout(typingTimeoutRef.current);
200
+ typingTimeoutRef.current = null;
201
+ if (typingActiveRef.current) {
202
+ typingActiveRef.current = false;
203
+ try {
204
+ onTyping == null ? void 0 : onTyping({ threadId: t, typing: false });
205
+ } catch {
206
+ }
207
+ }
123
208
  await onSendMessage({ threadId: t, text: msg });
124
209
  setText("");
125
210
  window.setTimeout(() => {
126
- var _a2;
127
- return (_a2 = endRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth", block: "end" });
211
+ var _a2, _b2;
212
+ try {
213
+ const el = messagesScrollRef.current;
214
+ if (el) el.scrollTop = el.scrollHeight;
215
+ else (_a2 = endRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "auto", block: "end" });
216
+ } catch {
217
+ (_b2 = endRef.current) == null ? void 0 : _b2.scrollIntoView({ behavior: "auto", block: "end" });
218
+ }
128
219
  }, 0);
129
220
  } finally {
130
221
  setSending(false);
@@ -260,7 +351,7 @@ function ChatCenter({
260
351
  /* @__PURE__ */ jsx("div", { className: "md:hidden", children: /* @__PURE__ */ jsx(Tooltip, { content: labelBack, placement: "left", offset: 10, children: /* @__PURE__ */ jsx(ActionIconButton, { title: labelBack, size: "sm", onClick: () => setMobileTab("threads"), className: "border border-[var(--border)] bg-[var(--card)] shadow-sm", children: /* @__PURE__ */ jsx(ArrowLeftIcon, { "aria-hidden": true }) }) }) })
261
352
  ] })
262
353
  ] }) }),
263
- /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto px-4 py-4", children: filteredMessages.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "grid place-items-center rounded-3xl border border-[var(--border)] bg-[color-mix(in_oklab,var(--card)_92%,transparent)] p-10 text-center", children: [
354
+ /* @__PURE__ */ jsx("div", { ref: messagesScrollRef, className: "min-h-0 flex-1 overflow-y-auto px-4 py-4", children: filteredMessages.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "grid place-items-center rounded-3xl border border-[var(--border)] bg-[color-mix(in_oklab,var(--card)_92%,transparent)] p-10 text-center", children: [
264
355
  /* @__PURE__ */ jsx("div", { className: "text-sm font-semibold text-[var(--foreground)]", children: emptyMessagesTitle }),
265
356
  /* @__PURE__ */ jsx("div", { className: "mt-1 text-xs text-[var(--muted)]", children: emptyMessagesSubtitle })
266
357
  ] }) : /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
@@ -303,36 +394,42 @@ function ChatCenter({
303
394
  }),
304
395
  /* @__PURE__ */ jsx("div", { ref: endRef })
305
396
  ] }) }),
306
- /* @__PURE__ */ jsx("div", { className: "border-t border-[var(--border)] bg-[var(--card)] px-4 py-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-2", children: [
307
- /* @__PURE__ */ jsx(
308
- Input,
309
- {
310
- ref: composerRef,
311
- value: text,
312
- onChange: (e) => setText(e.currentTarget.value),
313
- placeholder: composerPlaceholder,
314
- className: "flex-1",
315
- onKeyDown: (e) => {
316
- if (e.key === "Enter" && !e.shiftKey) {
317
- e.preventDefault();
318
- void send();
397
+ /* @__PURE__ */ jsxs("div", { className: "border-t border-[var(--border)] bg-[var(--card)] px-4 py-3", children: [
398
+ typingUsers && typingUsers.length ? /* @__PURE__ */ jsx("div", { className: "mb-2 flex items-center gap-2 px-1 text-[0.75rem] text-[var(--muted)]", children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
399
+ /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-[var(--primary)] opacity-80", "aria-hidden": true }),
400
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: typingUsers.length === 1 ? `${typingUsers[0].name} est\xE1 escribiendo\u2026` : `${typingUsers.slice(0, 2).map((u) => u.name).join(", ")}${typingUsers.length > 2 ? ` y ${typingUsers.length - 2} m\xE1s` : ""} est\xE1n escribiendo\u2026` })
401
+ ] }) }) : null,
402
+ /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-2", children: [
403
+ /* @__PURE__ */ jsx(
404
+ Input,
405
+ {
406
+ ref: composerRef,
407
+ value: text,
408
+ onChange: (e) => setTextWithTyping(e.currentTarget.value),
409
+ placeholder: composerPlaceholder,
410
+ className: "flex-1",
411
+ onKeyDown: (e) => {
412
+ if (e.key === "Enter" && !e.shiftKey) {
413
+ e.preventDefault();
414
+ void send();
415
+ }
319
416
  }
320
417
  }
321
- }
322
- ),
323
- /* @__PURE__ */ jsx(
324
- Button,
325
- {
326
- size: "sm",
327
- variant: "primary",
328
- disabled: !activeThreadId || !text.trim() || sending,
329
- loading: sending,
330
- onClick: () => void send(),
331
- leftIcon: /* @__PURE__ */ jsx(PaperPlaneIcon, { "aria-hidden": true, className: "h-4 w-4" }),
332
- children: labelSend
333
- }
334
- )
335
- ] }) })
418
+ ),
419
+ /* @__PURE__ */ jsx(
420
+ Button,
421
+ {
422
+ size: "sm",
423
+ variant: "primary",
424
+ disabled: !activeThreadId || !text.trim() || sending,
425
+ loading: sending,
426
+ onClick: () => void send(),
427
+ leftIcon: /* @__PURE__ */ jsx(PaperPlaneIcon, { "aria-hidden": true, className: "h-4 w-4" }),
428
+ children: labelSend
429
+ }
430
+ )
431
+ ] })
432
+ ] })
336
433
  ] });
337
434
  return /* @__PURE__ */ jsxs(Fragment, { children: [
338
435
  Trigger,
@@ -361,32 +458,25 @@ function ChatCenter({
361
458
  /* @__PURE__ */ jsx(ActionIconButton, { title: "Cerrar", size: "md", onClick: () => setOpen(false), className: "border border-[var(--border)] bg-[var(--card)] shadow-sm", children: /* @__PURE__ */ jsx(CloseIcon, { "aria-hidden": true, className: "h-4.5 w-4.5" }) })
362
459
  ] })
363
460
  ] }) }),
364
- /* @__PURE__ */ jsx(Drawer.Body, { className: "bg-[linear-gradient(180deg,color-mix(in_oklab,var(--surface)_65%,transparent),color-mix(in_oklab,var(--bg)_88%,transparent))]", children: /* @__PURE__ */ jsx("div", { className: "h-[calc(100vh-10rem)] min-h-[520px]", children: /* @__PURE__ */ jsxs("div", { className: "grid h-full grid-cols-1 md:grid-cols-[360px_1fr]", children: [
461
+ /* @__PURE__ */ jsx(Drawer.Body, { className: "overflow-hidden bg-[linear-gradient(180deg,color-mix(in_oklab,var(--surface)_65%,transparent),color-mix(in_oklab,var(--bg)_88%,transparent))]", children: /* @__PURE__ */ jsx("div", { className: "h-full min-h-0", children: /* @__PURE__ */ jsxs("div", { className: "grid h-full min-h-0 grid-cols-1 md:grid-cols-[360px_1fr]", children: [
365
462
  /* @__PURE__ */ jsx("div", { className: cx("h-full border-r border-[var(--border)] bg-[color-mix(in_oklab,var(--card)_90%,transparent)]", mobileTab === "threads" ? "" : "hidden md:block"), children: ThreadsPane }),
366
- /* @__PURE__ */ jsx("div", { className: cx("h-full", mobileTab === "chat" ? "" : "hidden md:block"), children: activeThread ? MessagesPane : /* @__PURE__ */ jsx("div", { className: "grid h-full place-items-center p-8", children: /* @__PURE__ */ jsxs("div", { className: "rounded-3xl border border-[var(--border)] bg-[color-mix(in_oklab,var(--card)_92%,transparent)] p-10 text-center", children: [
463
+ /* @__PURE__ */ jsx("div", { className: cx("h-full min-h-0", mobileTab === "chat" ? "" : "hidden md:block"), children: activeThread ? MessagesPane : /* @__PURE__ */ jsx("div", { className: "grid h-full place-items-center p-8", children: /* @__PURE__ */ jsxs("div", { className: "rounded-3xl border border-[var(--border)] bg-[color-mix(in_oklab,var(--card)_92%,transparent)] p-10 text-center", children: [
367
464
  /* @__PURE__ */ jsx("div", { className: "text-sm font-semibold text-[var(--foreground)]", children: "Selecciona un chat" }),
368
465
  /* @__PURE__ */ jsx("div", { className: "mt-1 text-xs text-[var(--muted)]", children: "Elige una conversaci\xF3n para ver mensajes." })
369
466
  ] }) }) })
370
467
  ] }) }) }),
371
- /* @__PURE__ */ jsx(Drawer.Footer, { sticky: true, className: "bg-[color-mix(in_oklab,var(--card)_90%,transparent)]", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
372
- /* @__PURE__ */ jsxs("div", { className: "text-xs text-[var(--muted)]", children: [
373
- "Conectado como ",
374
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-[var(--foreground)]", children: me.name })
375
- ] }),
376
- /* @__PURE__ */ jsx("div", { className: "md:hidden", children: /* @__PURE__ */ jsx(
377
- SegmentedTabs,
378
- {
379
- value: mobileTab,
380
- size: "sm",
381
- onChange: (v) => setMobileTab(v),
382
- options: [
383
- { value: "threads", label: "Chats" },
384
- { value: "chat", label: "Mensaje" }
385
- ]
386
- }
387
- ) }),
388
- /* @__PURE__ */ jsx("div", { className: "hidden md:block", children: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: () => setOpen(false), children: "Cerrar" }) })
389
- ] }) })
468
+ /* @__PURE__ */ jsx(Drawer.Footer, { sticky: true, className: "bg-[color-mix(in_oklab,var(--card)_90%,transparent)] md:hidden", children: /* @__PURE__ */ jsx(
469
+ SegmentedTabs,
470
+ {
471
+ value: mobileTab,
472
+ size: "sm",
473
+ onChange: (v) => setMobileTab(v),
474
+ options: [
475
+ { value: "threads", label: "Chats" },
476
+ { value: "chat", label: "Mensaje" }
477
+ ]
478
+ }
479
+ ) })
390
480
  ] })
391
481
  ] });
392
482
  }
package/dist/Sidebar.js CHANGED
@@ -744,6 +744,7 @@ function Sidebar({
744
744
  groups,
745
745
  mobile: true,
746
746
  activeKey,
747
+ toggleCollapsed,
747
748
  go,
748
749
  navEditable: navEnabled,
749
750
  navCustomizeLabel: navCustomizeButtonLabel,
@@ -1856,6 +1857,7 @@ function SidebarInner({
1856
1857
  const hasWorkspace = Boolean((workspaces == null ? void 0 : workspaces.length) && onWorkspaceSelect);
1857
1858
  const hasSlot = Boolean(sidebarSlot || sidebarSlotCollapsed);
1858
1859
  const showUser = Boolean(user || userMenuSlot);
1860
+ const showWorkspaceInTopPanel = Boolean(showBrand && hasWorkspace);
1859
1861
  const showTopPanel = Boolean((showBrand ? hasWorkspace : false) || showUser || hasSlot);
1860
1862
  const skGroups = Math.max(1, (_a = skeleton == null ? void 0 : skeleton.groups) != null ? _a : 4);
1861
1863
  const skItems = Math.max(2, (_b = skeleton == null ? void 0 : skeleton.itemsPerGroup) != null ? _b : 6);
@@ -1954,45 +1956,79 @@ function SidebarInner({
1954
1956
  ) }) : null
1955
1957
  ] });
1956
1958
  };
1959
+ const showCollapseToggle = !mobile && typeof toggleCollapsed === "function";
1960
+ const renderCollapseToggle = () => {
1961
+ if (!showCollapseToggle) return null;
1962
+ if (loading) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "fx-skeleton shrink-0", style: { width: 40, height: 40, borderRadius: 16 } });
1963
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Tooltip.default, { content: collapsed ? "Expandir barra lateral" : "Contraer barra lateral", placement: "right", offset: 10, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1964
+ import_Button.default,
1965
+ {
1966
+ noPaddingX: true,
1967
+ "aria-label": collapsed ? "Expandir barra lateral" : "Contraer barra lateral",
1968
+ onClick: toggleCollapsed,
1969
+ variant: "ghost",
1970
+ size: "sm",
1971
+ leftIcon: collapsed ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.CaretLineRightIcon, { className: "h-4 w-4", "aria-hidden": true }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.CaretLineLeftIcon, { className: "h-4 w-4", "aria-hidden": true }),
1972
+ className: "shrink-0 h-10 w-10 p-0 !gap-0 rounded-2xl border border-[var(--border)] bg-[var(--card)] text-[var(--foreground)] shadow-sm hover:bg-[color-mix(in_oklab,var(--surface)_70%,transparent)] active:scale-95",
1973
+ title: void 0
1974
+ }
1975
+ ) });
1976
+ };
1957
1977
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: ["flex h-full flex-col", loading ? "pointer-events-none select-none" : ""].join(" "), "aria-busy": loading ? "true" : void 0, children: [
1958
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between px-1", children: [
1959
- loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: ["flex items-center gap-3", collapsed ? "justify-center" : ""].join(" "), children: [
1978
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "px-1", children: loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2", children: [
1979
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: ["flex min-w-0 flex-1 items-center gap-3", collapsed ? "justify-center" : ""].join(" "), children: [
1960
1980
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "fx-skeleton", style: { width: collapsed ? 44 : 40, height: collapsed ? 44 : 40, borderRadius: 16 } }),
1961
1981
  !collapsed ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-2", children: [
1962
1982
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "fx-skeleton", style: { width: "8.5rem", height: 14, borderRadius: 10 } }),
1963
1983
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "fx-skeleton", style: { width: "10.5rem", height: 10, borderRadius: 10, opacity: 0.9 } })
1964
1984
  ] }) : null
1965
- ] }) : showBrand ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_Button.default, { unstyled: true, type: "button", onClick: onBrandClick, className: ["flex items-center gap-3", collapsed ? "justify-center" : ""].join(" "), title: "Ir al inicio", children: [
1966
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1967
- import_AvatarSquare.default,
1968
- {
1969
- size: collapsed ? 44 : 40,
1970
- src: brandLogoSrc != null ? brandLogoSrc : void 0,
1971
- alt: brandTitle,
1972
- initials: brandInitials,
1973
- imageFit: brandLogoSrc ? "contain" : "cover",
1974
- radiusClass: "rounded-2xl",
1975
- color,
1976
- className: "ring-0"
1977
- }
1978
- ),
1979
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_framer_motion.AnimatePresence, { initial: false, children: !collapsed && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1980
- import_framer_motion.motion.div,
1981
- {
1982
- initial: { opacity: 0, x: -6 },
1983
- animate: { opacity: 1, x: 0 },
1984
- exit: { opacity: 0, x: -6 },
1985
- transition: { duration: 0.15 },
1986
- className: "leading-tight",
1987
- children: [
1988
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-lg font-semibold tracking-tight", children: brandTitle }),
1989
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-[0.6875rem] uppercase tracking-wide text-[var(--muted)] text-left", children: brandSubtitle })
1990
- ]
1991
- },
1992
- "brand-meta"
1993
- ) })
1994
- ] }) : hasWorkspace ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "min-w-0 flex-1 pr-2", children: [
1995
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1985
+ ] }),
1986
+ renderCollapseToggle()
1987
+ ] }) : showBrand ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2", children: [
1988
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1989
+ import_Button.default,
1990
+ {
1991
+ unstyled: true,
1992
+ type: "button",
1993
+ onClick: onBrandClick,
1994
+ className: ["min-w-0 flex flex-1 items-center gap-3", collapsed ? "justify-center" : ""].join(" "),
1995
+ title: "Ir al inicio",
1996
+ children: [
1997
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1998
+ import_AvatarSquare.default,
1999
+ {
2000
+ size: collapsed ? 44 : 40,
2001
+ src: brandLogoSrc != null ? brandLogoSrc : void 0,
2002
+ alt: brandTitle,
2003
+ initials: brandInitials,
2004
+ imageFit: brandLogoSrc ? "contain" : "cover",
2005
+ radiusClass: "rounded-2xl",
2006
+ color,
2007
+ className: "ring-0"
2008
+ }
2009
+ ),
2010
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_framer_motion.AnimatePresence, { initial: false, children: !collapsed && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2011
+ import_framer_motion.motion.div,
2012
+ {
2013
+ initial: { opacity: 0, x: -6 },
2014
+ animate: { opacity: 1, x: 0 },
2015
+ exit: { opacity: 0, x: -6 },
2016
+ transition: { duration: 0.15 },
2017
+ className: "leading-tight",
2018
+ children: [
2019
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-lg font-semibold tracking-tight", children: brandTitle }),
2020
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-[0.6875rem] uppercase tracking-wide text-[var(--muted)] text-left", children: brandSubtitle })
2021
+ ]
2022
+ },
2023
+ "brand-meta"
2024
+ ) })
2025
+ ]
2026
+ }
2027
+ ),
2028
+ renderCollapseToggle()
2029
+ ] }) : hasWorkspace ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "min-w-0", children: [
2030
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2", children: [
2031
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1996
2032
  WorkspaceSwitcher,
1997
2033
  {
1998
2034
  collapsed,
@@ -2005,23 +2041,14 @@ function SidebarInner({
2005
2041
  variant: "panel",
2006
2042
  color
2007
2043
  }
2008
- ),
2009
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mt-2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SidebarTools, { condensed: collapsed }) })
2010
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-sm font-semibold text-[var(--muted)]", children: "Navegaci\xF3n" }),
2011
- !mobile && (loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hidden xl:inline-flex", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "fx-skeleton", style: { width: 32, height: 32, borderRadius: 12 } }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Tooltip.default, { content: collapsed ? "Expandir sidebar" : "Contraer sidebar", placement: "right", offset: 10, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2012
- import_Button.default,
2013
- {
2014
- noPaddingX: true,
2015
- "aria-label": collapsed ? "Expandir barra lateral" : "Contraer barra lateral",
2016
- onClick: toggleCollapsed,
2017
- variant: "ghost",
2018
- size: "sm",
2019
- leftIcon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.ChevronLeftRightIcon, { collapsed: collapsed != null ? collapsed : false }),
2020
- className: "hidden xl:inline-flex h-8 w-8 p-0 !gap-0 rounded-xl border border-[var(--border)] bg-[var(--card)] text-[var(--foreground)] shadow-sm hover:bg-[color-mix(in_oklab,var(--surface)_70%,transparent)] active:scale-[0.98]",
2021
- title: void 0
2022
- }
2023
- ) }))
2024
- ] }),
2044
+ ) }),
2045
+ renderCollapseToggle()
2046
+ ] }),
2047
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mt-2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SidebarTools, { condensed: collapsed }) })
2048
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2", children: [
2049
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: ["min-w-0 flex-1 text-sm font-semibold text-[var(--muted)]", collapsed ? "text-center" : ""].join(" "), children: "Navegaci\xF3n" }),
2050
+ renderCollapseToggle()
2051
+ ] }) }),
2025
2052
  showTopPanel || loading && skShowTopPanel ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: ["mt-4 px-1", collapsed ? "" : ""].join(" "), children: loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: collapsed ? "rounded-3xl border border-[var(--border)] bg-[color-mix(in_oklab,var(--card)_92%,transparent)] p-2 shadow-sm" : "overflow-hidden rounded-3xl border border-[var(--border)] bg-[color-mix(in_oklab,var(--card)_92%,transparent)] shadow-sm", children: collapsed ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col items-center justify-center gap-2", children: [
2026
2053
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "fx-skeleton", style: { width: 44, height: 44, borderRadius: 16 } }),
2027
2054
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "fx-skeleton", style: { width: 56, height: 12, borderRadius: 10 } })
@@ -2043,7 +2070,7 @@ function SidebarInner({
2043
2070
  ] })
2044
2071
  ] }) })
2045
2072
  ] }) }) : collapsed ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rounded-3xl border border-[var(--border)] bg-[color-mix(in_oklab,var(--card)_92%,transparent)] p-2 shadow-sm", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col items-center justify-center gap-2", children: [
2046
- hasWorkspace ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2073
+ showWorkspaceInTopPanel ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2047
2074
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2048
2075
  WorkspaceSwitcher,
2049
2076
  {
@@ -2076,7 +2103,7 @@ function SidebarInner({
2076
2103
  ] }) : null,
2077
2104
  sidebarSlotCollapsed ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex items-center justify-center", children: sidebarSlotCollapsed }) : null
2078
2105
  ] }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "overflow-hidden rounded-3xl border border-[var(--border)] bg-[color-mix(in_oklab,var(--card)_92%,transparent)] shadow-sm", children: [
2079
- hasWorkspace ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "px-2 py-2", children: [
2106
+ showWorkspaceInTopPanel ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "px-2 py-2", children: [
2080
2107
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2081
2108
  WorkspaceSwitcher,
2082
2109
  {
@@ -2092,7 +2119,7 @@ function SidebarInner({
2092
2119
  ),
2093
2120
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mt-2 px-1", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SidebarTools, {}) })
2094
2121
  ] }) : null,
2095
- hasWorkspace ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-px bg-[var(--border)] opacity-70" }) : null,
2122
+ showWorkspaceInTopPanel ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-px bg-[var(--border)] opacity-70" }) : null,
2096
2123
  showUser ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "px-2 py-2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-3 rounded-2xl px-2.5 py-2 hover:bg-[color-mix(in_oklab,var(--surface)_70%,transparent)]", children: [
2097
2124
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2098
2125
  import_AvatarSquare.default,
package/dist/Sidebar.mjs CHANGED
@@ -11,10 +11,11 @@ import Input from "./Input.mjs";
11
11
  import Select from "./Select.mjs";
12
12
  import Tooltip from "./Tooltip.mjs";
13
13
  import {
14
+ CaretLineLeftIcon,
15
+ CaretLineRightIcon,
14
16
  CheckIcon,
15
17
  ChevronDownIcon,
16
18
  CloseIcon,
17
- ChevronLeftRightIcon,
18
19
  DotsSixVerticalIcon,
19
20
  ListStarIcon,
20
21
  MagnifyingGlassIcon,
@@ -722,6 +723,7 @@ function Sidebar({
722
723
  groups,
723
724
  mobile: true,
724
725
  activeKey,
726
+ toggleCollapsed,
725
727
  go,
726
728
  navEditable: navEnabled,
727
729
  navCustomizeLabel: navCustomizeButtonLabel,
@@ -1834,6 +1836,7 @@ function SidebarInner({
1834
1836
  const hasWorkspace = Boolean((workspaces == null ? void 0 : workspaces.length) && onWorkspaceSelect);
1835
1837
  const hasSlot = Boolean(sidebarSlot || sidebarSlotCollapsed);
1836
1838
  const showUser = Boolean(user || userMenuSlot);
1839
+ const showWorkspaceInTopPanel = Boolean(showBrand && hasWorkspace);
1837
1840
  const showTopPanel = Boolean((showBrand ? hasWorkspace : false) || showUser || hasSlot);
1838
1841
  const skGroups = Math.max(1, (_a = skeleton == null ? void 0 : skeleton.groups) != null ? _a : 4);
1839
1842
  const skItems = Math.max(2, (_b = skeleton == null ? void 0 : skeleton.itemsPerGroup) != null ? _b : 6);
@@ -1932,45 +1935,79 @@ function SidebarInner({
1932
1935
  ) }) : null
1933
1936
  ] });
1934
1937
  };
1938
+ const showCollapseToggle = !mobile && typeof toggleCollapsed === "function";
1939
+ const renderCollapseToggle = () => {
1940
+ if (!showCollapseToggle) return null;
1941
+ if (loading) return /* @__PURE__ */ jsx("div", { className: "fx-skeleton shrink-0", style: { width: 40, height: 40, borderRadius: 16 } });
1942
+ return /* @__PURE__ */ jsx(Tooltip, { content: collapsed ? "Expandir barra lateral" : "Contraer barra lateral", placement: "right", offset: 10, children: /* @__PURE__ */ jsx(
1943
+ Button,
1944
+ {
1945
+ noPaddingX: true,
1946
+ "aria-label": collapsed ? "Expandir barra lateral" : "Contraer barra lateral",
1947
+ onClick: toggleCollapsed,
1948
+ variant: "ghost",
1949
+ size: "sm",
1950
+ leftIcon: collapsed ? /* @__PURE__ */ jsx(CaretLineRightIcon, { className: "h-4 w-4", "aria-hidden": true }) : /* @__PURE__ */ jsx(CaretLineLeftIcon, { className: "h-4 w-4", "aria-hidden": true }),
1951
+ className: "shrink-0 h-10 w-10 p-0 !gap-0 rounded-2xl border border-[var(--border)] bg-[var(--card)] text-[var(--foreground)] shadow-sm hover:bg-[color-mix(in_oklab,var(--surface)_70%,transparent)] active:scale-95",
1952
+ title: void 0
1953
+ }
1954
+ ) });
1955
+ };
1935
1956
  return /* @__PURE__ */ jsxs("div", { className: ["flex h-full flex-col", loading ? "pointer-events-none select-none" : ""].join(" "), "aria-busy": loading ? "true" : void 0, children: [
1936
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-1", children: [
1937
- loading ? /* @__PURE__ */ jsxs("div", { className: ["flex items-center gap-3", collapsed ? "justify-center" : ""].join(" "), children: [
1957
+ /* @__PURE__ */ jsx("div", { className: "px-1", children: loading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1958
+ /* @__PURE__ */ jsxs("div", { className: ["flex min-w-0 flex-1 items-center gap-3", collapsed ? "justify-center" : ""].join(" "), children: [
1938
1959
  /* @__PURE__ */ jsx("div", { className: "fx-skeleton", style: { width: collapsed ? 44 : 40, height: collapsed ? 44 : 40, borderRadius: 16 } }),
1939
1960
  !collapsed ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1940
1961
  /* @__PURE__ */ jsx("div", { className: "fx-skeleton", style: { width: "8.5rem", height: 14, borderRadius: 10 } }),
1941
1962
  /* @__PURE__ */ jsx("div", { className: "fx-skeleton", style: { width: "10.5rem", height: 10, borderRadius: 10, opacity: 0.9 } })
1942
1963
  ] }) : null
1943
- ] }) : showBrand ? /* @__PURE__ */ jsxs(Button, { unstyled: true, type: "button", onClick: onBrandClick, className: ["flex items-center gap-3", collapsed ? "justify-center" : ""].join(" "), title: "Ir al inicio", children: [
1944
- /* @__PURE__ */ jsx(
1945
- AvatarSquare,
1946
- {
1947
- size: collapsed ? 44 : 40,
1948
- src: brandLogoSrc != null ? brandLogoSrc : void 0,
1949
- alt: brandTitle,
1950
- initials: brandInitials,
1951
- imageFit: brandLogoSrc ? "contain" : "cover",
1952
- radiusClass: "rounded-2xl",
1953
- color,
1954
- className: "ring-0"
1955
- }
1956
- ),
1957
- /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: !collapsed && /* @__PURE__ */ jsxs(
1958
- motion.div,
1959
- {
1960
- initial: { opacity: 0, x: -6 },
1961
- animate: { opacity: 1, x: 0 },
1962
- exit: { opacity: 0, x: -6 },
1963
- transition: { duration: 0.15 },
1964
- className: "leading-tight",
1965
- children: [
1966
- /* @__PURE__ */ jsx("div", { className: "text-lg font-semibold tracking-tight", children: brandTitle }),
1967
- /* @__PURE__ */ jsx("div", { className: "text-[0.6875rem] uppercase tracking-wide text-[var(--muted)] text-left", children: brandSubtitle })
1968
- ]
1969
- },
1970
- "brand-meta"
1971
- ) })
1972
- ] }) : hasWorkspace ? /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 pr-2", children: [
1973
- /* @__PURE__ */ jsx(
1964
+ ] }),
1965
+ renderCollapseToggle()
1966
+ ] }) : showBrand ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1967
+ /* @__PURE__ */ jsxs(
1968
+ Button,
1969
+ {
1970
+ unstyled: true,
1971
+ type: "button",
1972
+ onClick: onBrandClick,
1973
+ className: ["min-w-0 flex flex-1 items-center gap-3", collapsed ? "justify-center" : ""].join(" "),
1974
+ title: "Ir al inicio",
1975
+ children: [
1976
+ /* @__PURE__ */ jsx(
1977
+ AvatarSquare,
1978
+ {
1979
+ size: collapsed ? 44 : 40,
1980
+ src: brandLogoSrc != null ? brandLogoSrc : void 0,
1981
+ alt: brandTitle,
1982
+ initials: brandInitials,
1983
+ imageFit: brandLogoSrc ? "contain" : "cover",
1984
+ radiusClass: "rounded-2xl",
1985
+ color,
1986
+ className: "ring-0"
1987
+ }
1988
+ ),
1989
+ /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: !collapsed && /* @__PURE__ */ jsxs(
1990
+ motion.div,
1991
+ {
1992
+ initial: { opacity: 0, x: -6 },
1993
+ animate: { opacity: 1, x: 0 },
1994
+ exit: { opacity: 0, x: -6 },
1995
+ transition: { duration: 0.15 },
1996
+ className: "leading-tight",
1997
+ children: [
1998
+ /* @__PURE__ */ jsx("div", { className: "text-lg font-semibold tracking-tight", children: brandTitle }),
1999
+ /* @__PURE__ */ jsx("div", { className: "text-[0.6875rem] uppercase tracking-wide text-[var(--muted)] text-left", children: brandSubtitle })
2000
+ ]
2001
+ },
2002
+ "brand-meta"
2003
+ ) })
2004
+ ]
2005
+ }
2006
+ ),
2007
+ renderCollapseToggle()
2008
+ ] }) : hasWorkspace ? /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
2009
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2010
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx(
1974
2011
  WorkspaceSwitcher,
1975
2012
  {
1976
2013
  collapsed,
@@ -1983,23 +2020,14 @@ function SidebarInner({
1983
2020
  variant: "panel",
1984
2021
  color
1985
2022
  }
1986
- ),
1987
- /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsx(SidebarTools, { condensed: collapsed }) })
1988
- ] }) : /* @__PURE__ */ jsx("div", { className: "text-sm font-semibold text-[var(--muted)]", children: "Navegaci\xF3n" }),
1989
- !mobile && (loading ? /* @__PURE__ */ jsx("div", { className: "hidden xl:inline-flex", children: /* @__PURE__ */ jsx("div", { className: "fx-skeleton", style: { width: 32, height: 32, borderRadius: 12 } }) }) : /* @__PURE__ */ jsx(Tooltip, { content: collapsed ? "Expandir sidebar" : "Contraer sidebar", placement: "right", offset: 10, children: /* @__PURE__ */ jsx(
1990
- Button,
1991
- {
1992
- noPaddingX: true,
1993
- "aria-label": collapsed ? "Expandir barra lateral" : "Contraer barra lateral",
1994
- onClick: toggleCollapsed,
1995
- variant: "ghost",
1996
- size: "sm",
1997
- leftIcon: /* @__PURE__ */ jsx(ChevronLeftRightIcon, { collapsed: collapsed != null ? collapsed : false }),
1998
- className: "hidden xl:inline-flex h-8 w-8 p-0 !gap-0 rounded-xl border border-[var(--border)] bg-[var(--card)] text-[var(--foreground)] shadow-sm hover:bg-[color-mix(in_oklab,var(--surface)_70%,transparent)] active:scale-[0.98]",
1999
- title: void 0
2000
- }
2001
- ) }))
2002
- ] }),
2023
+ ) }),
2024
+ renderCollapseToggle()
2025
+ ] }),
2026
+ /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsx(SidebarTools, { condensed: collapsed }) })
2027
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2028
+ /* @__PURE__ */ jsx("div", { className: ["min-w-0 flex-1 text-sm font-semibold text-[var(--muted)]", collapsed ? "text-center" : ""].join(" "), children: "Navegaci\xF3n" }),
2029
+ renderCollapseToggle()
2030
+ ] }) }),
2003
2031
  showTopPanel || loading && skShowTopPanel ? /* @__PURE__ */ jsx("div", { className: ["mt-4 px-1", collapsed ? "" : ""].join(" "), children: loading ? /* @__PURE__ */ jsx("div", { className: collapsed ? "rounded-3xl border border-[var(--border)] bg-[color-mix(in_oklab,var(--card)_92%,transparent)] p-2 shadow-sm" : "overflow-hidden rounded-3xl border border-[var(--border)] bg-[color-mix(in_oklab,var(--card)_92%,transparent)] shadow-sm", children: collapsed ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center gap-2", children: [
2004
2032
  /* @__PURE__ */ jsx("div", { className: "fx-skeleton", style: { width: 44, height: 44, borderRadius: 16 } }),
2005
2033
  /* @__PURE__ */ jsx("div", { className: "fx-skeleton", style: { width: 56, height: 12, borderRadius: 10 } })
@@ -2021,7 +2049,7 @@ function SidebarInner({
2021
2049
  ] })
2022
2050
  ] }) })
2023
2051
  ] }) }) : collapsed ? /* @__PURE__ */ jsx("div", { className: "rounded-3xl border border-[var(--border)] bg-[color-mix(in_oklab,var(--card)_92%,transparent)] p-2 shadow-sm", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center gap-2", children: [
2024
- hasWorkspace ? /* @__PURE__ */ jsxs(Fragment, { children: [
2052
+ showWorkspaceInTopPanel ? /* @__PURE__ */ jsxs(Fragment, { children: [
2025
2053
  /* @__PURE__ */ jsx(
2026
2054
  WorkspaceSwitcher,
2027
2055
  {
@@ -2054,7 +2082,7 @@ function SidebarInner({
2054
2082
  ] }) : null,
2055
2083
  sidebarSlotCollapsed ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: sidebarSlotCollapsed }) : null
2056
2084
  ] }) }) : /* @__PURE__ */ jsxs("div", { className: "overflow-hidden rounded-3xl border border-[var(--border)] bg-[color-mix(in_oklab,var(--card)_92%,transparent)] shadow-sm", children: [
2057
- hasWorkspace ? /* @__PURE__ */ jsxs("div", { className: "px-2 py-2", children: [
2085
+ showWorkspaceInTopPanel ? /* @__PURE__ */ jsxs("div", { className: "px-2 py-2", children: [
2058
2086
  /* @__PURE__ */ jsx(
2059
2087
  WorkspaceSwitcher,
2060
2088
  {
@@ -2070,7 +2098,7 @@ function SidebarInner({
2070
2098
  ),
2071
2099
  /* @__PURE__ */ jsx("div", { className: "mt-2 px-1", children: /* @__PURE__ */ jsx(SidebarTools, {}) })
2072
2100
  ] }) : null,
2073
- hasWorkspace ? /* @__PURE__ */ jsx("div", { className: "h-px bg-[var(--border)] opacity-70" }) : null,
2101
+ showWorkspaceInTopPanel ? /* @__PURE__ */ jsx("div", { className: "h-px bg-[var(--border)] opacity-70" }) : null,
2074
2102
  showUser ? /* @__PURE__ */ jsx("div", { className: "px-2 py-2", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 rounded-2xl px-2.5 py-2 hover:bg-[color-mix(in_oklab,var(--surface)_70%,transparent)]", children: [
2075
2103
  /* @__PURE__ */ jsx(
2076
2104
  AvatarSquare,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "framepexls-ui-lib",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "description": "Componentes UI de Framepexls para React/Next.",