@lindaui/blocks 0.3.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.
- package/dist/audio-player.d.ts +9 -0
- package/dist/audio-player.d.ts.map +1 -0
- package/dist/audio-player.js +73 -0
- package/dist/chat-avatar.d.ts +15 -0
- package/dist/chat-avatar.d.ts.map +1 -0
- package/dist/chat-avatar.js +42 -0
- package/dist/chat-date-divider.d.ts +6 -0
- package/dist/chat-date-divider.d.ts.map +1 -0
- package/dist/chat-date-divider.js +18 -0
- package/dist/chat-header.d.ts +9 -0
- package/dist/chat-header.d.ts.map +1 -0
- package/dist/chat-header.js +30 -0
- package/dist/chat-layout.d.ts +12 -0
- package/dist/chat-layout.d.ts.map +1 -0
- package/dist/chat-layout.js +24 -0
- package/dist/chat-time-divider.d.ts +6 -0
- package/dist/chat-time-divider.d.ts.map +1 -0
- package/dist/chat-time-divider.js +16 -0
- package/dist/chat-types.d.ts +37 -0
- package/dist/chat-types.d.ts.map +1 -0
- package/dist/chat-types.js +1 -0
- package/dist/chat-types.test.d.ts +2 -0
- package/dist/chat-types.test.d.ts.map +1 -0
- package/dist/conversation-list-item.d.ts +10 -0
- package/dist/conversation-list-item.d.ts.map +1 -0
- package/dist/conversation-list-item.js +53 -0
- package/dist/conversation-list.d.ts +16 -0
- package/dist/conversation-list.d.ts.map +1 -0
- package/dist/conversation-list.js +45 -0
- package/dist/conversation-thread.d.ts +11 -0
- package/dist/conversation-thread.d.ts.map +1 -0
- package/dist/conversation-thread.js +62 -0
- package/dist/empty-chat-view.d.ts +8 -0
- package/dist/empty-chat-view.d.ts.map +1 -0
- package/dist/empty-chat-view.js +28 -0
- package/dist/entity-health-card.d.ts +18 -0
- package/dist/entity-health-card.d.ts.map +1 -0
- package/dist/entity-health-card.js +47 -0
- package/dist/fetching-indicator.d.ts +6 -0
- package/dist/fetching-indicator.d.ts.map +1 -0
- package/dist/fetching-indicator.js +20 -0
- package/dist/message-bubble.d.ts +7 -0
- package/dist/message-bubble.d.ts.map +1 -0
- package/dist/message-bubble.js +83 -0
- package/dist/message-composer.d.ts +11 -0
- package/dist/message-composer.d.ts.map +1 -0
- package/dist/message-composer.js +41 -0
- package/dist/stat-card.d.ts +3 -1
- package/dist/stat-card.d.ts.map +1 -1
- package/dist/stat-card.js +20 -7
- package/dist/time-range-picker.d.ts +26 -0
- package/dist/time-range-picker.d.ts.map +1 -0
- package/dist/time-range-picker.js +86 -0
- package/llms.txt +62 -1
- package/package.json +73 -9
|
@@ -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 };
|
package/dist/stat-card.d.ts
CHANGED
|
@@ -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
|
package/dist/stat-card.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lindaui/blocks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Composed UI sections (auth, lists, master-detail, charts) built on @lindaui/ui — importable npm package, not a copy-paste registry.",
|
|
5
5
|
"author": "Franco Carballar <francocarballar@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -123,6 +123,70 @@
|
|
|
123
123
|
"./table-pane": {
|
|
124
124
|
"types": "./dist/table-pane.d.ts",
|
|
125
125
|
"import": "./dist/table-pane.js"
|
|
126
|
+
},
|
|
127
|
+
"./audio-player": {
|
|
128
|
+
"types": "./dist/audio-player.d.ts",
|
|
129
|
+
"import": "./dist/audio-player.js"
|
|
130
|
+
},
|
|
131
|
+
"./chat-avatar": {
|
|
132
|
+
"types": "./dist/chat-avatar.d.ts",
|
|
133
|
+
"import": "./dist/chat-avatar.js"
|
|
134
|
+
},
|
|
135
|
+
"./chat-date-divider": {
|
|
136
|
+
"types": "./dist/chat-date-divider.d.ts",
|
|
137
|
+
"import": "./dist/chat-date-divider.js"
|
|
138
|
+
},
|
|
139
|
+
"./chat-header": {
|
|
140
|
+
"types": "./dist/chat-header.d.ts",
|
|
141
|
+
"import": "./dist/chat-header.js"
|
|
142
|
+
},
|
|
143
|
+
"./chat-layout": {
|
|
144
|
+
"types": "./dist/chat-layout.d.ts",
|
|
145
|
+
"import": "./dist/chat-layout.js"
|
|
146
|
+
},
|
|
147
|
+
"./chat-time-divider": {
|
|
148
|
+
"types": "./dist/chat-time-divider.d.ts",
|
|
149
|
+
"import": "./dist/chat-time-divider.js"
|
|
150
|
+
},
|
|
151
|
+
"./chat-types": {
|
|
152
|
+
"types": "./dist/chat-types.d.ts",
|
|
153
|
+
"import": "./dist/chat-types.js"
|
|
154
|
+
},
|
|
155
|
+
"./conversation-list": {
|
|
156
|
+
"types": "./dist/conversation-list.d.ts",
|
|
157
|
+
"import": "./dist/conversation-list.js"
|
|
158
|
+
},
|
|
159
|
+
"./conversation-list-item": {
|
|
160
|
+
"types": "./dist/conversation-list-item.d.ts",
|
|
161
|
+
"import": "./dist/conversation-list-item.js"
|
|
162
|
+
},
|
|
163
|
+
"./conversation-thread": {
|
|
164
|
+
"types": "./dist/conversation-thread.d.ts",
|
|
165
|
+
"import": "./dist/conversation-thread.js"
|
|
166
|
+
},
|
|
167
|
+
"./empty-chat-view": {
|
|
168
|
+
"types": "./dist/empty-chat-view.d.ts",
|
|
169
|
+
"import": "./dist/empty-chat-view.js"
|
|
170
|
+
},
|
|
171
|
+
"./message-bubble": {
|
|
172
|
+
"types": "./dist/message-bubble.d.ts",
|
|
173
|
+
"import": "./dist/message-bubble.js"
|
|
174
|
+
},
|
|
175
|
+
"./message-composer": {
|
|
176
|
+
"types": "./dist/message-composer.d.ts",
|
|
177
|
+
"import": "./dist/message-composer.js"
|
|
178
|
+
},
|
|
179
|
+
"./time-range-picker": {
|
|
180
|
+
"types": "./dist/time-range-picker.d.ts",
|
|
181
|
+
"import": "./dist/time-range-picker.js"
|
|
182
|
+
},
|
|
183
|
+
"./entity-health-card": {
|
|
184
|
+
"types": "./dist/entity-health-card.d.ts",
|
|
185
|
+
"import": "./dist/entity-health-card.js"
|
|
186
|
+
},
|
|
187
|
+
"./fetching-indicator": {
|
|
188
|
+
"types": "./dist/fetching-indicator.d.ts",
|
|
189
|
+
"import": "./dist/fetching-indicator.js"
|
|
126
190
|
}
|
|
127
191
|
},
|
|
128
192
|
"files": [
|
|
@@ -142,7 +206,7 @@
|
|
|
142
206
|
},
|
|
143
207
|
"devDependencies": {
|
|
144
208
|
"bunchee": "^6.0.0",
|
|
145
|
-
"vitest": "^
|
|
209
|
+
"vitest": "^4.1.10",
|
|
146
210
|
"vite": "^6.4.3",
|
|
147
211
|
"@vitejs/plugin-react": "^4.3.0",
|
|
148
212
|
"@testing-library/react": "^16.0.0",
|
|
@@ -153,14 +217,14 @@
|
|
|
153
217
|
"react-dom": "^19.0.0",
|
|
154
218
|
"@types/react": "^19.0.0",
|
|
155
219
|
"@types/react-dom": "^19.0.0",
|
|
156
|
-
"react-hook-form": "^7.
|
|
157
|
-
"lucide-react": "^
|
|
158
|
-
"recharts": "^3.9.
|
|
159
|
-
"zod": "^
|
|
160
|
-
"@hookform/resolvers": "^
|
|
220
|
+
"react-hook-form": "^7.81.0",
|
|
221
|
+
"lucide-react": "^1.23.0",
|
|
222
|
+
"recharts": "^3.9.2",
|
|
223
|
+
"zod": "^4.4.3",
|
|
224
|
+
"@hookform/resolvers": "^5.4.0",
|
|
161
225
|
"typescript": "^6.0.3",
|
|
162
|
-
"@lindaui/
|
|
163
|
-
"@lindaui/
|
|
226
|
+
"@lindaui/tokens": "0.3.0",
|
|
227
|
+
"@lindaui/ui": "0.4.0"
|
|
164
228
|
},
|
|
165
229
|
"scripts": {
|
|
166
230
|
"build": "bunchee --no-dts && tsc -p tsconfig.build.json",
|