@lindaui/blocks 0.2.0 → 0.4.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.
Files changed (55) 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/message-bubble.d.ts +7 -0
  43. package/dist/message-bubble.d.ts.map +1 -0
  44. package/dist/message-bubble.js +83 -0
  45. package/dist/message-composer.d.ts +11 -0
  46. package/dist/message-composer.d.ts.map +1 -0
  47. package/dist/message-composer.js +41 -0
  48. package/dist/stat-card.d.ts +3 -1
  49. package/dist/stat-card.d.ts.map +1 -1
  50. package/dist/stat-card.js +20 -7
  51. package/dist/time-range-picker.d.ts +26 -0
  52. package/dist/time-range-picker.d.ts.map +1 -0
  53. package/dist/time-range-picker.js +86 -0
  54. package/llms.txt +62 -1
  55. package/package.json +89 -11
@@ -0,0 +1,47 @@
1
+ 'use client';
2
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
+ import { ChartCard } from './chart-card.js';
4
+ import { Badge } from '@lindaui/ui/badge';
5
+ import { Divider } from '@lindaui/ui/divider';
6
+
7
+ /** Lista de salud de entidades genérica (ex-GymHealthCard, generalizado). */ function EntityHealthCard({ title, description, items, emptyLabel = "Sin datos" }) {
8
+ return /*#__PURE__*/ jsx(ChartCard, {
9
+ title: title,
10
+ description: description,
11
+ children: items.length === 0 ? /*#__PURE__*/ jsx("p", {
12
+ className: "py-4 text-center text-sm text-muted-foreground",
13
+ children: emptyLabel
14
+ }) : /*#__PURE__*/ jsx("div", {
15
+ className: "flex flex-col",
16
+ children: items.map((item, i)=>/*#__PURE__*/ jsxs("div", {
17
+ children: [
18
+ i > 0 && /*#__PURE__*/ jsx(Divider, {}),
19
+ /*#__PURE__*/ jsxs("div", {
20
+ className: "flex items-center justify-between gap-2 py-2.5",
21
+ children: [
22
+ /*#__PURE__*/ jsx("span", {
23
+ className: "truncate text-sm",
24
+ children: item.label
25
+ }),
26
+ /*#__PURE__*/ jsxs("div", {
27
+ className: "flex shrink-0 items-center gap-2",
28
+ children: [
29
+ /*#__PURE__*/ jsx("span", {
30
+ className: "text-sm font-semibold tabular-nums",
31
+ children: item.value
32
+ }),
33
+ item.status && /*#__PURE__*/ jsx(Badge, {
34
+ variant: item.status,
35
+ children: item.statusLabel ?? item.status
36
+ })
37
+ ]
38
+ })
39
+ ]
40
+ })
41
+ ]
42
+ }, item.id))
43
+ })
44
+ });
45
+ }
46
+
47
+ export { EntityHealthCard };
@@ -0,0 +1,6 @@
1
+ export interface FetchingIndicatorProps {
2
+ label?: string;
3
+ }
4
+ /** Pill chico de "actualizando" para superponer sobre contenido ya cargado. */
5
+ export declare function FetchingIndicator({ label }: FetchingIndicatorProps): import("react").JSX.Element;
6
+ //# sourceMappingURL=fetching-indicator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetching-indicator.d.ts","sourceRoot":"","sources":["../src/fetching-indicator.tsx"],"names":[],"mappings":"AAGA,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,+EAA+E;AAC/E,wBAAgB,iBAAiB,CAAC,EAAE,KAAuB,EAAE,EAAE,sBAAsB,+BAOpF"}
@@ -0,0 +1,20 @@
1
+ 'use client';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { Spinner } from '@lindaui/ui/spinner';
4
+
5
+ /** Pill chico de "actualizando" para superponer sobre contenido ya cargado. */ function FetchingIndicator({ label = "Actualizando…" }) {
6
+ return /*#__PURE__*/ jsxs("div", {
7
+ className: "inline-flex items-center gap-1.5 rounded-full bg-secondary px-2.5 py-1 text-xs text-muted-foreground",
8
+ children: [
9
+ /*#__PURE__*/ jsx(Spinner, {
10
+ "aria-label": label,
11
+ className: "size-3"
12
+ }),
13
+ /*#__PURE__*/ jsx("span", {
14
+ children: label
15
+ })
16
+ ]
17
+ });
18
+ }
19
+
20
+ export { FetchingIndicator };
@@ -0,0 +1,7 @@
1
+ import type { GenericMessage } from "./chat-types.js";
2
+ export interface MessageBubbleProps {
3
+ message: GenericMessage;
4
+ onImageClick?: (url: string) => void;
5
+ }
6
+ export declare function MessageBubble({ message, onImageClick }: MessageBubbleProps): import("react").JSX.Element;
7
+ //# sourceMappingURL=message-bubble.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-bubble.d.ts","sourceRoot":"","sources":["../src/message-bubble.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAiB,MAAM,iBAAiB,CAAC;AAErE,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,cAAc,CAAC;IACxB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAUD,wBAAgB,aAAa,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,kBAAkB,+BAkD1E"}
@@ -0,0 +1,83 @@
1
+ 'use client';
2
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
+ import { formatShortTime } from '@lindaui/ui/date';
4
+ import { AlertCircle, CheckCheck, Check, Clock } from 'lucide-react';
5
+ import { AudioPlayer } from './audio-player.js';
6
+
7
+ const statusIcon = {
8
+ sending: {
9
+ icon: Clock,
10
+ label: "Enviando"
11
+ },
12
+ sent: {
13
+ icon: Check,
14
+ label: "Enviado"
15
+ },
16
+ delivered: {
17
+ icon: CheckCheck,
18
+ label: "Entregado"
19
+ },
20
+ read: {
21
+ icon: CheckCheck,
22
+ label: "Leído"
23
+ },
24
+ failed: {
25
+ icon: AlertCircle,
26
+ label: "Error al enviar"
27
+ }
28
+ };
29
+ function MessageBubble({ message, onImageClick }) {
30
+ const isOutgoing = message.side === "outgoing";
31
+ const status = message.status ? statusIcon[message.status] : null;
32
+ const StatusIcon = status?.icon;
33
+ return /*#__PURE__*/ jsx("div", {
34
+ className: `flex ${isOutgoing ? "justify-end" : "justify-start"}`,
35
+ children: /*#__PURE__*/ jsxs("div", {
36
+ className: `max-w-[75%] rounded-2xl px-3 py-2 text-sm ${isOutgoing ? "bg-primary text-primary-foreground" : "bg-secondary text-secondary-foreground"}`,
37
+ children: [
38
+ message.media?.kind === "image" && /*#__PURE__*/ jsx("button", {
39
+ type: "button",
40
+ onClick: ()=>onImageClick?.(message.media.url),
41
+ className: "-mx-1 -mt-1 mb-1 block overflow-hidden rounded-xl",
42
+ children: /*#__PURE__*/ jsx("img", {
43
+ src: message.media.url,
44
+ alt: message.media.alt ?? "",
45
+ className: "max-h-64 w-full object-cover"
46
+ })
47
+ }),
48
+ message.media?.kind === "audio" && /*#__PURE__*/ jsx("div", {
49
+ className: "mb-1",
50
+ children: /*#__PURE__*/ jsx(AudioPlayer, {
51
+ src: message.media.url,
52
+ durationSeconds: message.media.durationSeconds
53
+ })
54
+ }),
55
+ message.media?.kind === "pdf" && /*#__PURE__*/ jsx("a", {
56
+ href: message.media.url,
57
+ target: "_blank",
58
+ rel: "noreferrer",
59
+ className: "mb-1 block underline",
60
+ children: message.media.name ?? "Documento"
61
+ }),
62
+ message.text != null && /*#__PURE__*/ jsx("p", {
63
+ className: "whitespace-pre-wrap break-words",
64
+ children: message.text
65
+ }),
66
+ /*#__PURE__*/ jsxs("div", {
67
+ className: "mt-1 flex items-center justify-end gap-1 text-[10px] opacity-70",
68
+ children: [
69
+ /*#__PURE__*/ jsx("span", {
70
+ children: formatShortTime(message.sentAt)
71
+ }),
72
+ isOutgoing && StatusIcon && /*#__PURE__*/ jsx(StatusIcon, {
73
+ className: "size-3",
74
+ "aria-label": status.label
75
+ })
76
+ ]
77
+ })
78
+ ]
79
+ })
80
+ });
81
+ }
82
+
83
+ export { MessageBubble };
@@ -0,0 +1,11 @@
1
+ export interface MessageComposerProps {
2
+ value: string;
3
+ onValueChange: (value: string) => void;
4
+ onSend: () => void;
5
+ placeholder?: string;
6
+ isSending?: boolean;
7
+ isDisabled?: boolean;
8
+ sendLabel?: string;
9
+ }
10
+ export declare function MessageComposer({ value, onValueChange, onSend, placeholder, isSending, isDisabled, sendLabel, }: MessageComposerProps): import("react").JSX.Element;
11
+ //# sourceMappingURL=message-composer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-composer.d.ts","sourceRoot":"","sources":["../src/message-composer.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,aAAa,EACb,MAAM,EACN,WAAmC,EACnC,SAAiB,EACjB,UAAkB,EAClB,SAAoB,GACrB,EAAE,oBAAoB,+BA2BtB"}
@@ -0,0 +1,41 @@
1
+ 'use client';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { Send } from 'lucide-react';
4
+ import { Textarea } from '@lindaui/ui/textarea';
5
+ import { IconButton } from '@lindaui/ui/icon-button';
6
+
7
+ function MessageComposer({ value, onValueChange, onSend, placeholder = "Escribí un mensaje…", isSending = false, isDisabled = false, sendLabel = "Enviar" }) {
8
+ const canSend = value.trim().length > 0 && !isSending && !isDisabled;
9
+ const handleKeyDown = (e)=>{
10
+ if (e.key === "Enter" && !e.shiftKey) {
11
+ e.preventDefault();
12
+ if (canSend) onSend();
13
+ }
14
+ };
15
+ return /*#__PURE__*/ jsxs("div", {
16
+ className: "flex items-end gap-2 border-t border-border bg-card p-3",
17
+ children: [
18
+ /*#__PURE__*/ jsx("div", {
19
+ className: "flex-1",
20
+ children: /*#__PURE__*/ jsx(Textarea, {
21
+ "aria-label": placeholder,
22
+ placeholder: placeholder,
23
+ value: value,
24
+ onChange: onValueChange,
25
+ onKeyDown: handleKeyDown,
26
+ isDisabled: isDisabled
27
+ })
28
+ }),
29
+ /*#__PURE__*/ jsx(IconButton, {
30
+ "aria-label": sendLabel,
31
+ onPress: onSend,
32
+ isDisabled: !canSend,
33
+ children: /*#__PURE__*/ jsx(Send, {
34
+ className: "size-4"
35
+ })
36
+ })
37
+ ]
38
+ });
39
+ }
40
+
41
+ export { MessageComposer };
@@ -22,6 +22,8 @@ export interface StatCardProps {
22
22
  /** Tinte del icon box (y del valor en featured). Solo aplica a featured. */
23
23
  tone?: StatCardTone;
24
24
  className?: string;
25
+ /** Reemplaza value/delta/description por skeletons; label e icon quedan visibles. */
26
+ loading?: boolean;
25
27
  }
26
- export declare function StatCard({ label, value, delta, description, icon, sparkline, variant, tone, className, }: StatCardProps): import("react").JSX.Element;
28
+ export declare function StatCard({ label, value, delta, description, icon, sparkline, variant, tone, className, loading, }: StatCardProps): import("react").JSX.Element;
27
29
  //# sourceMappingURL=stat-card.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"stat-card.d.ts","sourceRoot":"","sources":["../src/stat-card.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAOvC,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAAC;CAClC;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEpF,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B;iFAC6E;IAC7E,OAAO,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;IACjC,4EAA4E;IAC5E,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAsDD,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,KAAK,EACL,KAAK,EACL,WAAW,EACX,IAAI,EACJ,SAAS,EACT,OAAmB,EACnB,IAAgB,EAChB,SAAc,GACf,EAAE,aAAa,+BA6Cf"}
1
+ {"version":3,"file":"stat-card.d.ts","sourceRoot":"","sources":["../src/stat-card.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAQvC,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAAC;CAClC;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEpF,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B;iFAC6E;IAC7E,OAAO,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;IACjC,4EAA4E;IAC5E,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qFAAqF;IACrF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAsDD,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,KAAK,EACL,KAAK,EACL,WAAW,EACX,IAAI,EACJ,SAAS,EACT,OAAmB,EACnB,IAAgB,EAChB,SAAc,EACd,OAAe,GAChB,EAAE,aAAa,+BA+Df"}
package/dist/stat-card.js CHANGED
@@ -3,6 +3,7 @@ import { jsx, jsxs } from 'react/jsx-runtime';
3
3
  import { Minus, TrendingDown, TrendingUp } from 'lucide-react';
4
4
  import { Card, CardContent, CardHeader, CardTitle } from '@lindaui/ui/card';
5
5
  import { Badge } from '@lindaui/ui/badge';
6
+ import { Skeleton } from '@lindaui/ui/skeleton';
6
7
  import { ChartContainer } from '@lindaui/ui/chart';
7
8
  import { LineChart, Line } from 'recharts';
8
9
 
@@ -79,7 +80,7 @@ function Sparkline({ sparkline }) {
79
80
  })
80
81
  });
81
82
  }
82
- function StatCard({ label, value, delta, description, icon, sparkline, variant = "default", tone = "neutral", className = "" }) {
83
+ function StatCard({ label, value, delta, description, icon, sparkline, variant = "default", tone = "neutral", className = "", loading = false }) {
83
84
  if (variant === "featured") {
84
85
  const t = toneStyles[tone];
85
86
  return /*#__PURE__*/ jsx(Card, {
@@ -94,12 +95,16 @@ function StatCard({ label, value, delta, description, icon, sparkline, variant =
94
95
  className: `grid size-11 shrink-0 place-items-center rounded-xl ${t.box}`,
95
96
  children: icon
96
97
  }),
97
- delta && /*#__PURE__*/ jsx(DeltaBadge, {
98
+ loading ? /*#__PURE__*/ jsx(Skeleton, {
99
+ className: "h-5 w-14 rounded-full"
100
+ }) : delta && /*#__PURE__*/ jsx(DeltaBadge, {
98
101
  delta: delta
99
102
  })
100
103
  ]
101
104
  }),
102
- /*#__PURE__*/ jsx("div", {
105
+ loading ? /*#__PURE__*/ jsx(Skeleton, {
106
+ className: "mt-4 h-8 w-20"
107
+ }) : /*#__PURE__*/ jsx("div", {
103
108
  className: `mt-4 text-3xl font-semibold tabular-nums ${t.value}`,
104
109
  children: value
105
110
  }),
@@ -107,7 +112,9 @@ function StatCard({ label, value, delta, description, icon, sparkline, variant =
107
112
  className: "mt-1 text-sm text-muted-foreground",
108
113
  children: label
109
114
  }),
110
- description && /*#__PURE__*/ jsx("p", {
115
+ loading ? /*#__PURE__*/ jsx(Skeleton, {
116
+ className: "mt-1.5 h-3 w-24"
117
+ }) : description && /*#__PURE__*/ jsx("p", {
111
118
  className: "mt-1.5 text-xs text-muted-foreground",
112
119
  children: description
113
120
  }),
@@ -140,17 +147,23 @@ function StatCard({ label, value, delta, description, icon, sparkline, variant =
140
147
  /*#__PURE__*/ jsxs(CardContent, {
141
148
  className: "pt-0",
142
149
  children: [
143
- /*#__PURE__*/ jsx("div", {
150
+ loading ? /*#__PURE__*/ jsx(Skeleton, {
151
+ className: "h-7 w-24"
152
+ }) : /*#__PURE__*/ jsx("div", {
144
153
  className: "text-2xl font-semibold tabular-nums sm:text-3xl",
145
154
  children: value
146
155
  }),
147
- delta && /*#__PURE__*/ jsx("div", {
156
+ loading ? /*#__PURE__*/ jsx(Skeleton, {
157
+ className: "mt-1.5 h-4 w-14 rounded-full"
158
+ }) : delta && /*#__PURE__*/ jsx("div", {
148
159
  className: "mt-1 flex items-center gap-1",
149
160
  children: /*#__PURE__*/ jsx(DeltaBadge, {
150
161
  delta: delta
151
162
  })
152
163
  }),
153
- description && /*#__PURE__*/ jsx("p", {
164
+ loading ? /*#__PURE__*/ jsx(Skeleton, {
165
+ className: "mt-1.5 h-3 w-28"
166
+ }) : description && /*#__PURE__*/ jsx("p", {
154
167
  className: "mt-1.5 text-xs text-muted-foreground",
155
168
  children: description
156
169
  }),
@@ -0,0 +1,26 @@
1
+ export interface TimeRangePreset {
2
+ key: string;
3
+ label: string;
4
+ }
5
+ export interface TimeRangeValue {
6
+ start: string;
7
+ end: string;
8
+ }
9
+ export interface TimeRangePickerProps {
10
+ presets?: TimeRangePreset[];
11
+ activePreset: string;
12
+ onPresetChange: (key: string) => void;
13
+ /** Key del preset que revela los inputs de rango custom. Default `"custom"`. */
14
+ customKey?: string;
15
+ range?: TimeRangeValue;
16
+ onRangeChange?: (range: TimeRangeValue) => void;
17
+ startLabel?: string;
18
+ endLabel?: string;
19
+ }
20
+ /**
21
+ * Presets + rango custom, controlado. El custom usa `<input type="date">` nativo
22
+ * (no el `date-range-picker` de HeroUI): evita traer `@internationalized/date` a
23
+ * un caso de uso simple de fecha-a-fecha.
24
+ */
25
+ export declare function TimeRangePicker({ presets, activePreset, onPresetChange, customKey, range, onRangeChange, startLabel, endLabel, }: TimeRangePickerProps): import("react").JSX.Element;
26
+ //# sourceMappingURL=time-range-picker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-range-picker.d.ts","sourceRoot":"","sources":["../src/time-range-picker.tsx"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,gFAAgF;IAChF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IAChD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAYD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAC9B,OAAyB,EACzB,YAAY,EACZ,cAAc,EACd,SAAoB,EACpB,KAAK,EACL,aAAa,EACb,UAAoB,EACpB,QAAkB,GACnB,EAAE,oBAAoB,+BAuCtB"}
@@ -0,0 +1,86 @@
1
+ 'use client';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { Button } from '@lindaui/ui/button';
4
+
5
+ const DEFAULT_PRESETS = [
6
+ {
7
+ key: "today",
8
+ label: "Hoy"
9
+ },
10
+ {
11
+ key: "7d",
12
+ label: "Últimos 7 días"
13
+ },
14
+ {
15
+ key: "30d",
16
+ label: "Últimos 30 días"
17
+ },
18
+ {
19
+ key: "custom",
20
+ label: "Personalizado"
21
+ }
22
+ ];
23
+ const fieldInputClass = "rounded-[var(--field-radius)] border border-[var(--field-border)] bg-[var(--field-background)] px-2 py-1 text-sm text-[var(--field-foreground)]";
24
+ /**
25
+ * Presets + rango custom, controlado. El custom usa `<input type="date">` nativo
26
+ * (no el `date-range-picker` de HeroUI): evita traer `@internationalized/date` a
27
+ * un caso de uso simple de fecha-a-fecha.
28
+ */ function TimeRangePicker({ presets = DEFAULT_PRESETS, activePreset, onPresetChange, customKey = "custom", range, onRangeChange, startLabel = "Desde", endLabel = "Hasta" }) {
29
+ return /*#__PURE__*/ jsxs("div", {
30
+ className: "flex flex-wrap items-center gap-2",
31
+ children: [
32
+ /*#__PURE__*/ jsx("div", {
33
+ className: "flex flex-wrap gap-2",
34
+ children: presets.map((p)=>/*#__PURE__*/ jsx(Button, {
35
+ variant: activePreset === p.key ? "primary" : "ghost",
36
+ size: "sm",
37
+ onPress: ()=>onPresetChange(p.key),
38
+ children: p.label
39
+ }, p.key))
40
+ }),
41
+ activePreset === customKey && /*#__PURE__*/ jsxs("div", {
42
+ className: "flex items-center gap-2",
43
+ children: [
44
+ /*#__PURE__*/ jsxs("label", {
45
+ className: "flex items-center gap-1.5 text-sm",
46
+ children: [
47
+ /*#__PURE__*/ jsx("span", {
48
+ className: "text-muted-foreground",
49
+ children: startLabel
50
+ }),
51
+ /*#__PURE__*/ jsx("input", {
52
+ type: "date",
53
+ value: range?.start ?? "",
54
+ onChange: (e)=>onRangeChange?.({
55
+ start: e.target.value,
56
+ end: range?.end ?? ""
57
+ }),
58
+ className: fieldInputClass
59
+ })
60
+ ]
61
+ }),
62
+ /*#__PURE__*/ jsxs("label", {
63
+ className: "flex items-center gap-1.5 text-sm",
64
+ children: [
65
+ /*#__PURE__*/ jsx("span", {
66
+ className: "text-muted-foreground",
67
+ children: endLabel
68
+ }),
69
+ /*#__PURE__*/ jsx("input", {
70
+ type: "date",
71
+ value: range?.end ?? "",
72
+ onChange: (e)=>onRangeChange?.({
73
+ start: range?.start ?? "",
74
+ end: e.target.value
75
+ }),
76
+ className: fieldInputClass
77
+ })
78
+ ]
79
+ })
80
+ ]
81
+ })
82
+ ]
83
+ });
84
+ }
85
+
86
+ export { TimeRangePicker };
package/llms.txt CHANGED
@@ -98,6 +98,57 @@ on a fixed dark `viewer-*` surface (for use over media); `inline` drops the abso
98
98
  positioning + forced dark bg, fills its container and inherits the panel surface with
99
99
  token-driven (legible) text. Content is identical across variants.
100
100
 
101
+ ## Chat (generic — data-agnostic, WhatsApp-style presentation)
102
+
103
+ Types live in `@lindaui/blocks/chat-types` (no runtime exports, just shapes):
104
+ `GenericMessage` (`id`, `side: "incoming"|"outgoing"`, `text?`, `media?:
105
+ {kind:"image"|"audio"|"pdf",...}`, `sentAt`, `status?`), `ConversationSummary`
106
+ (`id`, `title`, `subtitle?`, `avatarUrl?`, `lastMessageAt?`, `unreadCount?`). Map your
107
+ domain (WhatsApp threads, tickets, whatever) into these before passing them down.
108
+
109
+ ### @lindaui/blocks/chat-layout
110
+ Responsive 3-column shell (`sidebar`/`main`/`detail?`); mobile shows one panel at a
111
+ time via `activePanel?: "sidebar"|"main"|"detail"` (default `"sidebar"`).
112
+
113
+ ### @lindaui/blocks/chat-header
114
+ Row: `avatar?`, `title`, `subtitle?`, `actions?`.
115
+
116
+ ### @lindaui/blocks/chat-avatar
117
+ `<ChatAvatar name src? size? onError? />` — initials fallback (bespoke, does NOT
118
+ compose `@lindaui/ui/avatar`: that wrapper only exposes the HeroUI Root, no
119
+ `.Image`/`.Fallback`, so it can't do a real fallback-on-error).
120
+
121
+ ### @lindaui/blocks/conversation-list · conversation-list-item
122
+ `<ConversationList conversations={ConversationSummary[]} selectedId onSelect
123
+ searchValue onSearchChange loading emptyContent filters />` — search field + scrollable
124
+ rows; does NOT filter for you. `ConversationListItem` is the single row, usable
125
+ standalone.
126
+
127
+ ### @lindaui/blocks/conversation-thread
128
+ `<ConversationThread messages={GenericMessage[]} loading onImageClick emptyTitle
129
+ emptyDescription />` — auto-scrolls to the last message; groups by calendar day via
130
+ `@lindaui/ui/time-grouping` + `chat-date-divider`.
131
+
132
+ ### @lindaui/blocks/message-bubble
133
+ Renders one `GenericMessage`: text, image (click → `onImageClick(url)`), audio (via
134
+ `audio-player`), or pdf (link). Side-aligned; outgoing messages show a status icon.
135
+
136
+ ### @lindaui/blocks/message-composer
137
+ `<MessageComposer value onValueChange onSend placeholder isSending isDisabled
138
+ sendLabel />` — Enter sends, Shift+Enter inserts a newline.
139
+
140
+ ### @lindaui/blocks/audio-player
141
+ `<AudioPlayer src durationSeconds? playLabel? pauseLabel? />` — voice-note player:
142
+ play/pause + a static (non-analyzed) waveform + duration.
143
+
144
+ ### @lindaui/blocks/chat-date-divider · chat-time-divider
145
+ Centered day pill ("Hoy"/"Ayer"/short date, via `@lindaui/ui/date`) and a smaller
146
+ sticky time-group header, respectively.
147
+
148
+ ### @lindaui/blocks/empty-chat-view
149
+ Centered empty state (icon + title + description) — also the `conversation-thread`
150
+ empty state.
151
+
101
152
  ## Documents
102
153
 
103
154
  ### @lindaui/blocks/document-panel
@@ -129,6 +180,16 @@ const config = { sales: { label: "Sales", color: "var(--chart-1)" } };
129
180
  - `@lindaui/blocks/pie-chart`
130
181
  - `@lindaui/blocks/radar-chart`
131
182
  - `@lindaui/blocks/radial-chart`
132
- - `@lindaui/blocks/stat-card` — KPI card: label/value/delta/sparkline
183
+ - `@lindaui/blocks/stat-card` — KPI card: label/value/delta/sparkline. `loading?`
184
+ swaps value/delta/description for skeletons (label/icon stay visible).
133
185
  - `@lindaui/blocks/chart-card` — Card shell wrapping a chart
134
186
  - `@lindaui/blocks/stats-grid` — responsive grid of KPI stat-cards
187
+ - `@lindaui/blocks/entity-health-card` — generic health list on the `chart-card`
188
+ shell: `items: {id,label,value,status?,statusLabel?}[]` (status → a `Badge`).
189
+ - `@lindaui/blocks/time-range-picker` — presets (`{key,label}[]`, default
190
+ today/7d/30d/custom) + a custom range revealed when `activePreset === customKey`
191
+ (default `"custom"`), using native `<input type="date">` (no
192
+ `@internationalized/date`). Controlled: `activePreset`, `onPresetChange`, `range:
193
+ {start,end}` (ISO date strings), `onRangeChange`.
194
+ - `@lindaui/blocks/fetching-indicator` — small "Actualizando…" pill with a spinner,
195
+ for overlaying on already-loaded content (distinct from a full loading state).