@lindaui/blocks 0.3.0 → 0.4.1

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.
Files changed (56) hide show
  1. package/dist/audio-player.d.ts +9 -0
  2. package/dist/audio-player.d.ts.map +1 -0
  3. package/dist/audio-player.js +73 -0
  4. package/dist/chat-avatar.d.ts +15 -0
  5. package/dist/chat-avatar.d.ts.map +1 -0
  6. package/dist/chat-avatar.js +42 -0
  7. package/dist/chat-date-divider.d.ts +6 -0
  8. package/dist/chat-date-divider.d.ts.map +1 -0
  9. package/dist/chat-date-divider.js +18 -0
  10. package/dist/chat-header.d.ts +9 -0
  11. package/dist/chat-header.d.ts.map +1 -0
  12. package/dist/chat-header.js +30 -0
  13. package/dist/chat-layout.d.ts +12 -0
  14. package/dist/chat-layout.d.ts.map +1 -0
  15. package/dist/chat-layout.js +24 -0
  16. package/dist/chat-time-divider.d.ts +6 -0
  17. package/dist/chat-time-divider.d.ts.map +1 -0
  18. package/dist/chat-time-divider.js +16 -0
  19. package/dist/chat-types.d.ts +37 -0
  20. package/dist/chat-types.d.ts.map +1 -0
  21. package/dist/chat-types.js +1 -0
  22. package/dist/chat-types.test.d.ts +2 -0
  23. package/dist/chat-types.test.d.ts.map +1 -0
  24. package/dist/conversation-list-item.d.ts +10 -0
  25. package/dist/conversation-list-item.d.ts.map +1 -0
  26. package/dist/conversation-list-item.js +53 -0
  27. package/dist/conversation-list.d.ts +16 -0
  28. package/dist/conversation-list.d.ts.map +1 -0
  29. package/dist/conversation-list.js +45 -0
  30. package/dist/conversation-thread.d.ts +11 -0
  31. package/dist/conversation-thread.d.ts.map +1 -0
  32. package/dist/conversation-thread.js +62 -0
  33. package/dist/empty-chat-view.d.ts +8 -0
  34. package/dist/empty-chat-view.d.ts.map +1 -0
  35. package/dist/empty-chat-view.js +28 -0
  36. package/dist/entity-health-card.d.ts +18 -0
  37. package/dist/entity-health-card.d.ts.map +1 -0
  38. package/dist/entity-health-card.js +47 -0
  39. package/dist/fetching-indicator.d.ts +6 -0
  40. package/dist/fetching-indicator.d.ts.map +1 -0
  41. package/dist/fetching-indicator.js +20 -0
  42. package/dist/list-pane.js +2 -2
  43. package/dist/message-bubble.d.ts +7 -0
  44. package/dist/message-bubble.d.ts.map +1 -0
  45. package/dist/message-bubble.js +83 -0
  46. package/dist/message-composer.d.ts +11 -0
  47. package/dist/message-composer.d.ts.map +1 -0
  48. package/dist/message-composer.js +41 -0
  49. package/dist/stat-card.d.ts +3 -1
  50. package/dist/stat-card.d.ts.map +1 -1
  51. package/dist/stat-card.js +20 -7
  52. package/dist/time-range-picker.d.ts +26 -0
  53. package/dist/time-range-picker.d.ts.map +1 -0
  54. package/dist/time-range-picker.js +86 -0
  55. package/llms.txt +62 -1
  56. package/package.json +90 -25
@@ -0,0 +1,9 @@
1
+ export interface AudioPlayerProps {
2
+ src: string;
3
+ durationSeconds?: number;
4
+ playLabel?: string;
5
+ pauseLabel?: string;
6
+ }
7
+ /** Reproductor de nota de voz genérico: play/pause + waveform estática + duración. */
8
+ export declare function AudioPlayer({ src, durationSeconds, playLabel, pauseLabel, }: AudioPlayerProps): import("react").JSX.Element;
9
+ //# sourceMappingURL=audio-player.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audio-player.d.ts","sourceRoot":"","sources":["../src/audio-player.tsx"],"names":[],"mappings":"AAKA,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAeD,sFAAsF;AACtF,wBAAgB,WAAW,CAAC,EAC1B,GAAG,EACH,eAAe,EACf,SAAwB,EACxB,UAAqB,GACtB,EAAE,gBAAgB,+BA0ClB"}
@@ -0,0 +1,73 @@
1
+ 'use client';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { useRef, useState } from 'react';
4
+ import { Pause, Play } from 'lucide-react';
5
+ import { IconButton } from '@lindaui/ui/icon-button';
6
+
7
+ function formatDuration(seconds) {
8
+ const m = Math.floor(seconds / 60);
9
+ const s = Math.floor(seconds % 60);
10
+ return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
11
+ }
12
+ // Alturas deterministas (mismo seed que recording-overlay) — barra estática,
13
+ // no una waveform real del audio.
14
+ const BAR_HEIGHTS = Array.from({
15
+ length: 24
16
+ }, (_, i)=>{
17
+ const seed = Math.abs(Math.sin(i * 12.9898) * 43758.5453) % 1;
18
+ return Math.max(20, Math.round((0.25 + seed * 0.75) * 100));
19
+ });
20
+ /** Reproductor de nota de voz genérico: play/pause + waveform estática + duración. */ function AudioPlayer({ src, durationSeconds, playLabel = "Reproducir", pauseLabel = "Pausar" }) {
21
+ const audioRef = useRef(null);
22
+ const [isPlaying, setIsPlaying] = useState(false);
23
+ const [currentTime, setCurrentTime] = useState(0);
24
+ const toggle = ()=>{
25
+ const audio = audioRef.current;
26
+ if (!audio) return;
27
+ if (isPlaying) audio.pause();
28
+ else void audio.play();
29
+ };
30
+ return /*#__PURE__*/ jsxs("div", {
31
+ className: "flex items-center gap-2.5 rounded-full bg-secondary px-2 py-1.5",
32
+ children: [
33
+ /*#__PURE__*/ jsx("audio", {
34
+ ref: audioRef,
35
+ src: src,
36
+ onPlay: ()=>setIsPlaying(true),
37
+ onPause: ()=>setIsPlaying(false),
38
+ onEnded: ()=>setIsPlaying(false),
39
+ onTimeUpdate: (e)=>setCurrentTime(e.currentTarget.currentTime),
40
+ children: /*#__PURE__*/ jsx("track", {
41
+ kind: "captions"
42
+ })
43
+ }),
44
+ /*#__PURE__*/ jsx(IconButton, {
45
+ "aria-label": isPlaying ? pauseLabel : playLabel,
46
+ onPress: toggle,
47
+ variant: "ghost",
48
+ size: "sm",
49
+ children: isPlaying ? /*#__PURE__*/ jsx(Pause, {
50
+ className: "size-4"
51
+ }) : /*#__PURE__*/ jsx(Play, {
52
+ className: "size-4"
53
+ })
54
+ }),
55
+ /*#__PURE__*/ jsx("div", {
56
+ className: "flex h-6 flex-1 items-end gap-[2px]",
57
+ "aria-hidden": "true",
58
+ children: BAR_HEIGHTS.map((h, i)=>/*#__PURE__*/ jsx("span", {
59
+ className: "w-[2.5px] rounded-full bg-primary/60",
60
+ style: {
61
+ height: `${h}%`
62
+ }
63
+ }, i))
64
+ }),
65
+ /*#__PURE__*/ jsx("span", {
66
+ className: "font-mono text-[11px] tabular-nums text-muted-foreground",
67
+ children: formatDuration(durationSeconds ?? currentTime)
68
+ })
69
+ ]
70
+ });
71
+ }
72
+
73
+ export { AudioPlayer };
@@ -0,0 +1,15 @@
1
+ export type ChatAvatarSize = "sm" | "md" | "lg";
2
+ export interface ChatAvatarProps {
3
+ src?: string;
4
+ name: string;
5
+ size?: ChatAvatarSize;
6
+ /** Se dispara cuando `src` falla en cargar (además del fallback automático a iniciales). */
7
+ onError?: () => void;
8
+ }
9
+ /**
10
+ * Avatar de chat con fallback a iniciales. Bespoke (no compone
11
+ * `@lindaui/ui/avatar`): ese wrapper solo expone el Root de HeroUI, sin
12
+ * `Avatar.Image`/`Avatar.Fallback` — no alcanza para un fallback-on-error real.
13
+ */
14
+ export declare function ChatAvatar({ src, name, size, onError }: ChatAvatarProps): import("react").JSX.Element;
15
+ //# sourceMappingURL=chat-avatar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-avatar.d.ts","sourceRoot":"","sources":["../src/chat-avatar.tsx"],"names":[],"mappings":"AAGA,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEhD,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,4FAA4F;IAC5F,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAeD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,IAAW,EAAE,OAAO,EAAE,EAAE,eAAe,+BAyB9E"}
@@ -0,0 +1,42 @@
1
+ 'use client';
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import { useState } from 'react';
4
+
5
+ const sizeClasses = {
6
+ sm: "size-8 text-xs",
7
+ md: "size-10 text-sm",
8
+ lg: "size-12 text-base"
9
+ };
10
+ function initials(name) {
11
+ const parts = name.trim().split(/\s+/).filter(Boolean);
12
+ if (parts.length === 0) return "?";
13
+ if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
14
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
15
+ }
16
+ /**
17
+ * Avatar de chat con fallback a iniciales. Bespoke (no compone
18
+ * `@lindaui/ui/avatar`): ese wrapper solo expone el Root de HeroUI, sin
19
+ * `Avatar.Image`/`Avatar.Fallback` — no alcanza para un fallback-on-error real.
20
+ */ function ChatAvatar({ src, name, size = "md", onError }) {
21
+ const [broken, setBroken] = useState(false);
22
+ const showImage = src != null && !broken;
23
+ return /*#__PURE__*/ jsx("div", {
24
+ role: "img",
25
+ "aria-label": name,
26
+ className: `grid shrink-0 place-items-center overflow-hidden rounded-full bg-secondary font-medium text-secondary-foreground ${sizeClasses[size]}`,
27
+ children: showImage ? /*#__PURE__*/ jsx("img", {
28
+ src: src,
29
+ alt: "",
30
+ className: "h-full w-full object-cover",
31
+ onError: ()=>{
32
+ setBroken(true);
33
+ onError?.();
34
+ }
35
+ }) : /*#__PURE__*/ jsx("span", {
36
+ "aria-hidden": "true",
37
+ children: initials(name)
38
+ })
39
+ });
40
+ }
41
+
42
+ export { ChatAvatar };
@@ -0,0 +1,6 @@
1
+ export interface ChatDateDividerProps {
2
+ date: Date | number | string;
3
+ }
4
+ /** Divider centrado de día ("Hoy"/"Ayer"/fecha corta) para timelines de chat. */
5
+ export declare function ChatDateDivider({ date }: ChatDateDividerProps): import("react").JSX.Element;
6
+ //# sourceMappingURL=chat-date-divider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-date-divider.d.ts","sourceRoot":"","sources":["../src/chat-date-divider.tsx"],"names":[],"mappings":"AAGA,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;CAC9B;AAED,iFAAiF;AACjF,wBAAgB,eAAe,CAAC,EAAE,IAAI,EAAE,EAAE,oBAAoB,+BAS7D"}
@@ -0,0 +1,18 @@
1
+ 'use client';
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import { formatRelativeTime } from '@lindaui/ui/date';
4
+
5
+ /** Divider centrado de día ("Hoy"/"Ayer"/fecha corta) para timelines de chat. */ function ChatDateDivider({ date }) {
6
+ const label = formatRelativeTime(date);
7
+ return /*#__PURE__*/ jsx("div", {
8
+ className: "my-3 flex items-center justify-center",
9
+ role: "separator",
10
+ "aria-label": label,
11
+ children: /*#__PURE__*/ jsx("span", {
12
+ className: "rounded-full bg-secondary px-3 py-1 text-xs font-medium text-secondary-foreground",
13
+ children: label
14
+ })
15
+ });
16
+ }
17
+
18
+ export { ChatDateDivider };
@@ -0,0 +1,9 @@
1
+ import type { ReactNode } from "react";
2
+ export interface ChatHeaderProps {
3
+ avatar?: ReactNode;
4
+ title: ReactNode;
5
+ subtitle?: ReactNode;
6
+ actions?: ReactNode;
7
+ }
8
+ export declare function ChatHeader({ avatar, title, subtitle, actions }: ChatHeaderProps): import("react").JSX.Element;
9
+ //# sourceMappingURL=chat-header.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-header.d.ts","sourceRoot":"","sources":["../src/chat-header.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,KAAK,EAAE,SAAS,CAAC;IACjB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,OAAO,CAAC,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,UAAU,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,eAAe,+BAa/E"}
@@ -0,0 +1,30 @@
1
+ 'use client';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+
4
+ function ChatHeader({ avatar, title, subtitle, actions }) {
5
+ return /*#__PURE__*/ jsxs("div", {
6
+ className: "flex items-center gap-3 border-b border-border bg-card px-4 py-3",
7
+ children: [
8
+ avatar,
9
+ /*#__PURE__*/ jsxs("div", {
10
+ className: "min-w-0 flex-1",
11
+ children: [
12
+ /*#__PURE__*/ jsx("div", {
13
+ className: "truncate text-sm font-semibold",
14
+ children: title
15
+ }),
16
+ subtitle != null && /*#__PURE__*/ jsx("div", {
17
+ className: "truncate text-xs text-muted-foreground",
18
+ children: subtitle
19
+ })
20
+ ]
21
+ }),
22
+ actions != null && /*#__PURE__*/ jsx("div", {
23
+ className: "flex shrink-0 items-center gap-1",
24
+ children: actions
25
+ })
26
+ ]
27
+ });
28
+ }
29
+
30
+ export { ChatHeader };
@@ -0,0 +1,12 @@
1
+ import type { ReactNode } from "react";
2
+ export type ChatLayoutPanel = "sidebar" | "main" | "detail";
3
+ export interface ChatLayoutProps {
4
+ sidebar: ReactNode;
5
+ main: ReactNode;
6
+ detail?: ReactNode;
7
+ /** Panel visible en mobile (un solo panel a la vez). Default "sidebar". */
8
+ activePanel?: ChatLayoutPanel;
9
+ }
10
+ /** Shell responsive de 3 columnas (sidebar/main/detail); mobile-first, un panel a la vez. */
11
+ export declare function ChatLayout({ sidebar, main, detail, activePanel }: ChatLayoutProps): import("react").JSX.Element;
12
+ //# sourceMappingURL=chat-layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-layout.d.ts","sourceRoot":"","sources":["../src/chat-layout.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;AAE5D,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,SAAS,CAAC;IACnB,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B;AAED,6FAA6F;AAC7F,wBAAgB,UAAU,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,WAAuB,EAAE,EAAE,eAAe,+BA0B7F"}
@@ -0,0 +1,24 @@
1
+ 'use client';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+
4
+ /** Shell responsive de 3 columnas (sidebar/main/detail); mobile-first, un panel a la vez. */ function ChatLayout({ sidebar, main, detail, activePanel = "sidebar" }) {
5
+ return /*#__PURE__*/ jsxs("div", {
6
+ className: "flex h-full min-h-0 w-full overflow-hidden",
7
+ children: [
8
+ /*#__PURE__*/ jsx("div", {
9
+ className: `w-full shrink-0 border-r border-border sm:block sm:w-80 ${activePanel === "sidebar" ? "block" : "hidden sm:block"}`,
10
+ children: sidebar
11
+ }),
12
+ /*#__PURE__*/ jsx("div", {
13
+ className: `min-w-0 flex-1 sm:block ${activePanel === "main" ? "block" : "hidden sm:block"}`,
14
+ children: main
15
+ }),
16
+ detail != null && /*#__PURE__*/ jsx("div", {
17
+ className: `w-full shrink-0 border-l border-border lg:block lg:w-96 ${activePanel === "detail" ? "block" : "hidden lg:block"}`,
18
+ children: detail
19
+ })
20
+ ]
21
+ });
22
+ }
23
+
24
+ export { ChatLayout };
@@ -0,0 +1,6 @@
1
+ export interface ChatTimeDividerProps {
2
+ label: string;
3
+ }
4
+ /** Header sticky de grupo horario (variante más chica que {@link ChatDateDivider}). */
5
+ export declare function ChatTimeDivider({ label }: ChatTimeDividerProps): import("react").JSX.Element;
6
+ //# sourceMappingURL=chat-time-divider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-time-divider.d.ts","sourceRoot":"","sources":["../src/chat-time-divider.tsx"],"names":[],"mappings":"AAEA,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,uFAAuF;AACvF,wBAAgB,eAAe,CAAC,EAAE,KAAK,EAAE,EAAE,oBAAoB,+BAY9D"}
@@ -0,0 +1,16 @@
1
+ 'use client';
2
+ import { jsx } from 'react/jsx-runtime';
3
+
4
+ /** Header sticky de grupo horario (variante más chica que {@link ChatDateDivider}). */ function ChatTimeDivider({ label }) {
5
+ return /*#__PURE__*/ jsx("div", {
6
+ className: "sticky top-0 z-10 flex justify-center bg-card/80 py-1.5 backdrop-blur",
7
+ role: "separator",
8
+ "aria-label": label,
9
+ children: /*#__PURE__*/ jsx("span", {
10
+ className: "rounded-full bg-secondary px-2.5 py-0.5 text-[11px] font-medium text-secondary-foreground",
11
+ children: label
12
+ })
13
+ });
14
+ }
15
+
16
+ export { ChatTimeDivider };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Tipos compartidos por la familia `chat-*`. Agnósticos de dominio — el
3
+ * consumidor mapea su shape real (mensajes de WhatsApp, tickets, etc.) a
4
+ * estos antes de pasarlos a los blocks.
5
+ */
6
+ export type MessageSide = "incoming" | "outgoing";
7
+ export type MessageStatus = "sending" | "sent" | "delivered" | "read" | "failed";
8
+ export type GenericMessageMedia = {
9
+ kind: "image";
10
+ url: string;
11
+ alt?: string;
12
+ } | {
13
+ kind: "audio";
14
+ url: string;
15
+ durationSeconds?: number;
16
+ } | {
17
+ kind: "pdf";
18
+ url: string;
19
+ name?: string;
20
+ };
21
+ export interface GenericMessage {
22
+ id: string;
23
+ side: MessageSide;
24
+ text?: string;
25
+ media?: GenericMessageMedia;
26
+ sentAt: Date | number | string;
27
+ status?: MessageStatus;
28
+ }
29
+ export interface ConversationSummary {
30
+ id: string;
31
+ title: string;
32
+ subtitle?: string;
33
+ avatarUrl?: string;
34
+ lastMessageAt?: Date | number | string;
35
+ unreadCount?: number;
36
+ }
37
+ //# sourceMappingURL=chat-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-types.d.ts","sourceRoot":"","sources":["../src/chat-types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,UAAU,CAAC;AAElD,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEjF,MAAM,MAAM,mBAAmB,GAC3B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,GACxD;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhD,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,mBAAmB,CAAC;IAC5B,MAAM,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;IAC/B,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB"}
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=chat-types.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-types.test.d.ts","sourceRoot":"","sources":["../src/chat-types.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ import type { ReactNode } from "react";
2
+ import type { ConversationSummary } from "./chat-types.js";
3
+ export interface ConversationListItemProps {
4
+ conversation: ConversationSummary;
5
+ selected?: boolean;
6
+ onSelect?: () => void;
7
+ trailing?: ReactNode;
8
+ }
9
+ export declare function ConversationListItem({ conversation, selected, onSelect, trailing, }: ConversationListItemProps): import("react").JSX.Element;
10
+ //# sourceMappingURL=conversation-list-item.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conversation-list-item.d.ts","sourceRoot":"","sources":["../src/conversation-list-item.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAE3D,MAAM,WAAW,yBAAyB;IACxC,YAAY,EAAE,mBAAmB,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,wBAAgB,oBAAoB,CAAC,EACnC,YAAY,EACZ,QAAgB,EAChB,QAAQ,EACR,QAAQ,GACT,EAAE,yBAAyB,+BAkC3B"}
@@ -0,0 +1,53 @@
1
+ 'use client';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { formatRelativeTime } from '@lindaui/ui/date';
4
+ import { ChatAvatar } from './chat-avatar.js';
5
+
6
+ function ConversationListItem({ conversation, selected = false, onSelect, trailing }) {
7
+ return /*#__PURE__*/ jsxs("button", {
8
+ type: "button",
9
+ "aria-pressed": selected,
10
+ onClick: onSelect,
11
+ className: `flex w-full items-center gap-3 border-b border-border px-3 py-3 text-left font-[inherit] text-[inherit] transition-colors ${selected ? "bg-primary/5" : "bg-card hover:bg-accent/50"}`,
12
+ children: [
13
+ /*#__PURE__*/ jsx(ChatAvatar, {
14
+ name: conversation.title,
15
+ src: conversation.avatarUrl
16
+ }),
17
+ /*#__PURE__*/ jsxs("div", {
18
+ className: "min-w-0 flex-1",
19
+ children: [
20
+ /*#__PURE__*/ jsxs("div", {
21
+ className: "flex items-center justify-between gap-2",
22
+ children: [
23
+ /*#__PURE__*/ jsx("span", {
24
+ className: "truncate text-sm font-semibold",
25
+ children: conversation.title
26
+ }),
27
+ conversation.lastMessageAt != null && /*#__PURE__*/ jsx("span", {
28
+ className: "shrink-0 text-[11px] text-muted-foreground",
29
+ children: formatRelativeTime(conversation.lastMessageAt)
30
+ })
31
+ ]
32
+ }),
33
+ /*#__PURE__*/ jsxs("div", {
34
+ className: "flex items-center justify-between gap-2",
35
+ children: [
36
+ conversation.subtitle != null && /*#__PURE__*/ jsx("span", {
37
+ className: "truncate text-xs text-muted-foreground",
38
+ children: conversation.subtitle
39
+ }),
40
+ !!conversation.unreadCount && /*#__PURE__*/ jsx("span", {
41
+ className: "grid size-5 shrink-0 place-items-center rounded-full bg-primary text-[10px] font-semibold text-primary-foreground",
42
+ children: conversation.unreadCount
43
+ })
44
+ ]
45
+ })
46
+ ]
47
+ }),
48
+ trailing
49
+ ]
50
+ });
51
+ }
52
+
53
+ export { ConversationListItem };
@@ -0,0 +1,16 @@
1
+ import type { ReactNode } from "react";
2
+ import type { ConversationSummary } from "./chat-types.js";
3
+ export interface ConversationListProps {
4
+ conversations: ConversationSummary[];
5
+ selectedId?: string;
6
+ onSelect?: (id: string) => void;
7
+ searchValue?: string;
8
+ onSearchChange?: (value: string) => void;
9
+ searchLabel?: string;
10
+ searchPlaceholder?: string;
11
+ loading?: boolean;
12
+ emptyContent?: ReactNode;
13
+ filters?: ReactNode;
14
+ }
15
+ export declare function ConversationList({ conversations, selectedId, onSelect, searchValue, onSearchChange, searchLabel, searchPlaceholder, loading, emptyContent, filters, }: ConversationListProps): import("react").JSX.Element;
16
+ //# sourceMappingURL=conversation-list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conversation-list.d.ts","sourceRoot":"","sources":["../src/conversation-list.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAKvC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAE3D,MAAM,WAAW,qBAAqB;IACpC,aAAa,EAAE,mBAAmB,EAAE,CAAC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,OAAO,CAAC,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,aAAa,EACb,UAAU,EACV,QAAQ,EACR,WAAW,EACX,cAAc,EACd,WAAsB,EACtB,iBAA0C,EAC1C,OAAe,EACf,YAAY,EACZ,OAAO,GACR,EAAE,qBAAqB,+BAkCvB"}
@@ -0,0 +1,45 @@
1
+ 'use client';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { SearchField } from '@lindaui/ui/search-field';
4
+ import { ScrollShadow } from '@lindaui/ui/scroll-shadow';
5
+ import { Spinner } from '@lindaui/ui/spinner';
6
+ import { ConversationListItem } from './conversation-list-item.js';
7
+
8
+ function ConversationList({ conversations, selectedId, onSelect, searchValue, onSearchChange, searchLabel = "Buscar", searchPlaceholder = "Buscar conversación…", loading = false, emptyContent, filters }) {
9
+ return /*#__PURE__*/ jsxs("div", {
10
+ className: "flex h-full flex-col",
11
+ children: [
12
+ /*#__PURE__*/ jsx("div", {
13
+ className: "border-b border-border p-3",
14
+ children: /*#__PURE__*/ jsx(SearchField, {
15
+ "aria-label": searchLabel,
16
+ placeholder: searchPlaceholder,
17
+ value: searchValue,
18
+ onChange: onSearchChange
19
+ })
20
+ }),
21
+ filters != null && /*#__PURE__*/ jsx("div", {
22
+ className: "border-b border-border px-3 py-2",
23
+ children: filters
24
+ }),
25
+ /*#__PURE__*/ jsx(ScrollShadow, {
26
+ className: "flex-1",
27
+ children: loading ? /*#__PURE__*/ jsx("div", {
28
+ className: "flex items-center justify-center p-8",
29
+ children: /*#__PURE__*/ jsx(Spinner, {
30
+ "aria-label": "Cargando"
31
+ })
32
+ }) : conversations.length === 0 ? /*#__PURE__*/ jsx("div", {
33
+ className: "p-8 text-center text-sm text-muted-foreground",
34
+ children: emptyContent ?? "Sin conversaciones"
35
+ }) : conversations.map((c)=>/*#__PURE__*/ jsx(ConversationListItem, {
36
+ conversation: c,
37
+ selected: c.id === selectedId,
38
+ onSelect: ()=>onSelect?.(c.id)
39
+ }, c.id))
40
+ })
41
+ ]
42
+ });
43
+ }
44
+
45
+ export { ConversationList };
@@ -0,0 +1,11 @@
1
+ import type { GenericMessage } from "./chat-types.js";
2
+ export interface ConversationThreadProps {
3
+ messages: GenericMessage[];
4
+ loading?: boolean;
5
+ onImageClick?: (url: string) => void;
6
+ emptyTitle?: string;
7
+ emptyDescription?: string;
8
+ }
9
+ /** Timeline scrolleable con auto-scroll al último mensaje + agrupado por día. */
10
+ export declare function ConversationThread({ messages, loading, onImageClick, emptyTitle, emptyDescription, }: ConversationThreadProps): import("react").JSX.Element;
11
+ //# sourceMappingURL=conversation-thread.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conversation-thread.d.ts","sourceRoot":"","sources":["../src/conversation-thread.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,iFAAiF;AACjF,wBAAgB,kBAAkB,CAAC,EACjC,QAAQ,EACR,OAAe,EACf,YAAY,EACZ,UAA2B,EAC3B,gBAAgB,GACjB,EAAE,uBAAuB,+BAsCzB"}
@@ -0,0 +1,62 @@
1
+ 'use client';
2
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
+ import { useRef, useEffect } from 'react';
4
+ import { ScrollShadow } from '@lindaui/ui/scroll-shadow';
5
+ import { Spinner } from '@lindaui/ui/spinner';
6
+ import { groupByDay } from '@lindaui/ui/time-grouping';
7
+ import { ChatDateDivider } from './chat-date-divider.js';
8
+ import { MessageBubble } from './message-bubble.js';
9
+ import { EmptyChatView } from './empty-chat-view.js';
10
+
11
+ /** Timeline scrolleable con auto-scroll al último mensaje + agrupado por día. */ function ConversationThread({ messages, loading = false, onImageClick, emptyTitle = "Sin mensajes", emptyDescription }) {
12
+ const bottomRef = useRef(null);
13
+ useEffect(()=>{
14
+ bottomRef.current?.scrollIntoView({
15
+ block: "end"
16
+ });
17
+ }, [
18
+ messages.length
19
+ ]);
20
+ if (loading) {
21
+ return /*#__PURE__*/ jsx("div", {
22
+ className: "flex h-full items-center justify-center",
23
+ children: /*#__PURE__*/ jsx(Spinner, {
24
+ "aria-label": "Cargando mensajes"
25
+ })
26
+ });
27
+ }
28
+ if (messages.length === 0) {
29
+ return /*#__PURE__*/ jsx(EmptyChatView, {
30
+ title: emptyTitle,
31
+ description: emptyDescription
32
+ });
33
+ }
34
+ const groups = groupByDay(messages, (m)=>m.sentAt);
35
+ return /*#__PURE__*/ jsx(ScrollShadow, {
36
+ className: "h-full",
37
+ children: /*#__PURE__*/ jsxs("div", {
38
+ className: "flex flex-col gap-1 p-4",
39
+ children: [
40
+ groups.map((group)=>/*#__PURE__*/ jsxs("div", {
41
+ children: [
42
+ /*#__PURE__*/ jsx(ChatDateDivider, {
43
+ date: group.items[0].sentAt
44
+ }),
45
+ /*#__PURE__*/ jsx("div", {
46
+ className: "flex flex-col gap-1.5",
47
+ children: group.items.map((message)=>/*#__PURE__*/ jsx(MessageBubble, {
48
+ message: message,
49
+ onImageClick: onImageClick
50
+ }, message.id))
51
+ })
52
+ ]
53
+ }, group.key)),
54
+ /*#__PURE__*/ jsx("div", {
55
+ ref: bottomRef
56
+ })
57
+ ]
58
+ })
59
+ });
60
+ }
61
+
62
+ export { ConversationThread };
@@ -0,0 +1,8 @@
1
+ import type { ReactNode } from "react";
2
+ export interface EmptyChatViewProps {
3
+ title?: string;
4
+ description?: ReactNode;
5
+ icon?: ReactNode;
6
+ }
7
+ export declare function EmptyChatView({ title, description, icon, }: EmptyChatViewProps): import("react").JSX.Element;
8
+ //# sourceMappingURL=empty-chat-view.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"empty-chat-view.d.ts","sourceRoot":"","sources":["../src/empty-chat-view.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;AAED,wBAAgB,aAAa,CAAC,EAC5B,KAAgC,EAChC,WAAW,EACX,IAAI,GACL,EAAE,kBAAkB,+BAUpB"}
@@ -0,0 +1,28 @@
1
+ 'use client';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { MessageCircle } from 'lucide-react';
4
+
5
+ function EmptyChatView({ title = "Elegí una conversación", description, icon }) {
6
+ return /*#__PURE__*/ jsxs("div", {
7
+ className: "flex h-full flex-col items-center justify-center gap-3 p-8 text-center text-muted-foreground",
8
+ children: [
9
+ /*#__PURE__*/ jsx("div", {
10
+ className: "grid size-14 place-items-center rounded-full bg-secondary",
11
+ children: icon ?? /*#__PURE__*/ jsx(MessageCircle, {
12
+ className: "size-6",
13
+ "aria-hidden": "true"
14
+ })
15
+ }),
16
+ /*#__PURE__*/ jsx("p", {
17
+ className: "text-sm font-medium text-foreground",
18
+ children: title
19
+ }),
20
+ description != null && /*#__PURE__*/ jsx("p", {
21
+ className: "max-w-xs text-xs",
22
+ children: description
23
+ })
24
+ ]
25
+ });
26
+ }
27
+
28
+ export { EmptyChatView };
@@ -0,0 +1,18 @@
1
+ import type { ReactNode } from "react";
2
+ export type EntityHealthStatus = "default" | "success" | "warning" | "danger" | "info";
3
+ export interface EntityHealthItem {
4
+ id: string;
5
+ label: string;
6
+ value: ReactNode;
7
+ status?: EntityHealthStatus;
8
+ statusLabel?: string;
9
+ }
10
+ export interface EntityHealthCardProps {
11
+ title: ReactNode;
12
+ description?: ReactNode;
13
+ items: EntityHealthItem[];
14
+ emptyLabel?: string;
15
+ }
16
+ /** Lista de salud de entidades genérica (ex-GymHealthCard, generalizado). */
17
+ export declare function EntityHealthCard({ title, description, items, emptyLabel, }: EntityHealthCardProps): import("react").JSX.Element;
18
+ //# sourceMappingURL=entity-health-card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-health-card.d.ts","sourceRoot":"","sources":["../src/entity-health-card.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAKvC,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEvF,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,CAAC;IACjB,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,SAAS,CAAC;IACjB,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,WAAW,EACX,KAAK,EACL,UAAwB,GACzB,EAAE,qBAAqB,+BAuBvB"}