pixelon 0.1.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 (66) hide show
  1. package/README.md +54 -0
  2. package/dist/App.d.ts +3 -0
  3. package/dist/App.js +6 -0
  4. package/dist/assets/output-BpGRJPjR.css +1 -0
  5. package/dist/assets/output-DGLCW84k.css +1 -0
  6. package/dist/components/atoms/AttachmentPreview/AttachmentPreview.d.ts +8 -0
  7. package/dist/components/atoms/AttachmentPreview/AttachmentPreview.js +44 -0
  8. package/dist/components/atoms/Avatar/Avatar.d.ts +10 -0
  9. package/dist/components/atoms/Avatar/Avatar.js +28 -0
  10. package/dist/components/atoms/Button/Button.d.ts +10 -0
  11. package/dist/components/atoms/Button/Button.js +137 -0
  12. package/dist/components/atoms/DateDivider/DateDivider.d.ts +7 -0
  13. package/dist/components/atoms/DateDivider/DateDivider.js +40 -0
  14. package/dist/components/atoms/DateDivider/index.d.ts +2 -0
  15. package/dist/components/atoms/DateDivider/index.js +1 -0
  16. package/dist/components/atoms/Input/Input.d.ts +10 -0
  17. package/dist/components/atoms/Input/Input.js +111 -0
  18. package/dist/components/atoms/Link/Link.d.ts +14 -0
  19. package/dist/components/atoms/Link/Link.js +16 -0
  20. package/dist/components/atoms/SelectMenu/SelectMenu.d.ts +9 -0
  21. package/dist/components/atoms/SelectMenu/SelectMenu.js +23 -0
  22. package/dist/components/atoms/Text/Text.d.ts +11 -0
  23. package/dist/components/atoms/Text/Text.js +93 -0
  24. package/dist/components/atoms/Textarea/Textarea.d.ts +6 -0
  25. package/dist/components/atoms/Textarea/Textarea.js +5 -0
  26. package/dist/components/atoms/logo/Logo.d.ts +6 -0
  27. package/dist/components/atoms/logo/Logo.js +5 -0
  28. package/dist/components/molecules/chat/AssistantMessage/AssistantMessage.d.ts +7 -0
  29. package/dist/components/molecules/chat/AssistantMessage/AssistantMessage.js +10 -0
  30. package/dist/components/molecules/chat/AssistantThinkingIndicator/AssistantThinkingIndicator.d.ts +3 -0
  31. package/dist/components/molecules/chat/AssistantThinkingIndicator/AssistantThinkingIndicator.js +36 -0
  32. package/dist/components/molecules/chat/ChatHeader/ChatHeader.d.ts +6 -0
  33. package/dist/components/molecules/chat/ChatHeader/ChatHeader.js +6 -0
  34. package/dist/components/molecules/chat/ChatInput/ChatInput.d.ts +17 -0
  35. package/dist/components/molecules/chat/ChatInput/ChatInput.js +56 -0
  36. package/dist/components/molecules/chat/FileViewer/FileViewer.d.ts +7 -0
  37. package/dist/components/molecules/chat/FileViewer/FileViewer.js +41 -0
  38. package/dist/components/molecules/chat/FullscreenViewer/FullscreenViewer.d.ts +10 -0
  39. package/dist/components/molecules/chat/FullscreenViewer/FullscreenViewer.js +65 -0
  40. package/dist/components/molecules/chat/MessageStatus/MessageStatus.d.ts +9 -0
  41. package/dist/components/molecules/chat/MessageStatus/MessageStatus.js +14 -0
  42. package/dist/components/molecules/chat/Modal/Modal.d.ts +12 -0
  43. package/dist/components/molecules/chat/Modal/Modal.js +29 -0
  44. package/dist/components/molecules/chat/UserAttachments/UserAttachments.d.ts +6 -0
  45. package/dist/components/molecules/chat/UserAttachments/UserAttachments.js +8 -0
  46. package/dist/components/molecules/chat/UserMessage/UserMessage.d.ts +11 -0
  47. package/dist/components/molecules/chat/UserMessage/UserMessage.js +8 -0
  48. package/dist/components/organisms/chat/Chat.d.ts +21 -0
  49. package/dist/components/organisms/chat/Chat.js +278 -0
  50. package/dist/components/organisms/chat/index.d.ts +1 -0
  51. package/dist/components/organisms/chat/index.js +1 -0
  52. package/dist/index.d.ts +171 -0
  53. package/dist/index.esm.js +21664 -0
  54. package/dist/index.esm.js.map +1 -0
  55. package/dist/index.js +21666 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/main.d.ts +1 -0
  58. package/dist/main.js +6 -0
  59. package/dist/styles.css +1 -0
  60. package/dist/styles.css.map +1 -0
  61. package/dist/test/setup.d.ts +1 -0
  62. package/dist/test/setup.js +20 -0
  63. package/package.json +95 -0
  64. package/src/styles/_variables.scss +84 -0
  65. package/src/styles/global.scss +58 -0
  66. package/src/styles/index.scss +30 -0
@@ -0,0 +1,111 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ const inputStyles = {
3
+ wrapper: {
4
+ display: 'flex',
5
+ flexDirection: 'column',
6
+ gap: 'var(--spacing-xs)',
7
+ },
8
+ wrapperFullWidth: {
9
+ width: '100%',
10
+ },
11
+ wrapperError: {
12
+ // Los estilos de error se aplican al input interno
13
+ },
14
+ wrapperChat: {
15
+ // Los estilos de chat se aplican al input interno
16
+ },
17
+ label: {
18
+ fontSize: 'var(--font-size-sm)',
19
+ fontWeight: 'var(--font-weight-medium)',
20
+ color: 'var(--color-text)',
21
+ },
22
+ container: {
23
+ position: 'relative',
24
+ display: 'flex',
25
+ alignItems: 'center',
26
+ },
27
+ icon: {
28
+ position: 'absolute',
29
+ left: 'var(--spacing-md)',
30
+ display: 'flex',
31
+ alignItems: 'center',
32
+ justifyContent: 'center',
33
+ color: 'var(--color-text-secondary)',
34
+ },
35
+ error: {
36
+ fontSize: 'var(--font-size-sm)',
37
+ color: 'var(--color-error)',
38
+ },
39
+ input: {
40
+ width: '100%',
41
+ padding: 'var(--spacing-sm) var(--spacing-md)',
42
+ fontFamily: 'var(--font-family-base)',
43
+ fontSize: 'var(--font-size-base)',
44
+ lineHeight: 'var(--line-height-normal)',
45
+ color: 'var(--color-text)',
46
+ backgroundColor: 'var(--color-background)',
47
+ border: '1px solid var(--color-border)',
48
+ borderRadius: 'var(--border-radius-md)',
49
+ transition: 'all 0.2s ease-in-out',
50
+ '&::placeholder': {
51
+ color: 'var(--color-text-secondary)',
52
+ },
53
+ '&:focus': {
54
+ outline: 'none',
55
+ borderColor: 'var(--color-primary)',
56
+ boxShadow: '0 0 0 2px rgba(0, 0, 0, 0.1)',
57
+ },
58
+ '&:disabled': {
59
+ backgroundColor: 'var(--color-background-alt)',
60
+ cursor: 'not-allowed',
61
+ },
62
+ },
63
+ inputWithIcon: {
64
+ paddingLeft: 'calc(var(--spacing-md) * 2 + 16px)',
65
+ },
66
+ inputChat: {
67
+ backgroundColor: 'var(--color-chat-input-bg)',
68
+ border: '1px solid var(--color-chat-input-border)',
69
+ borderRadius: 'var(--border-radius-lg)',
70
+ padding: '0 var(--spacing-lg)',
71
+ fontSize: 'var(--font-size-base)',
72
+ lineHeight: 'var(--line-height-relaxed)',
73
+ height: '40px',
74
+ minHeight: '40px',
75
+ boxSizing: 'border-box',
76
+ display: 'flex',
77
+ alignItems: 'center',
78
+ '&:focus': {
79
+ borderColor: 'var(--color-chat-input-focus)',
80
+ boxShadow: '0 0 0 2px rgba(0, 0, 0, 0.1)',
81
+ },
82
+ },
83
+ inputError: {
84
+ borderColor: 'var(--color-error)',
85
+ '&:focus': {
86
+ borderColor: 'var(--color-error)',
87
+ boxShadow: '0 0 0 2px rgba(239, 68, 68, 0.1)',
88
+ },
89
+ },
90
+ };
91
+ const getInputStyles = (props) => {
92
+ const { fullWidth = false, error, variant = 'default', icon } = props;
93
+ const wrapperStyle = {
94
+ ...inputStyles.wrapper,
95
+ ...(fullWidth && inputStyles.wrapperFullWidth),
96
+ ...(error && inputStyles.wrapperError),
97
+ ...(variant === 'chat' && inputStyles.wrapperChat),
98
+ };
99
+ const inputStyle = {
100
+ ...inputStyles.input,
101
+ ...(variant === 'chat' && inputStyles.inputChat),
102
+ ...(error && inputStyles.inputError),
103
+ ...(icon && inputStyles.inputWithIcon),
104
+ };
105
+ return { wrapperStyle, inputStyle };
106
+ };
107
+ export const Input = ({ label, error, icon, fullWidth = false, variant = 'default', className = '', ...props }) => {
108
+ const { wrapperStyle, inputStyle } = getInputStyles({ fullWidth, error, variant, icon });
109
+ return (_jsxs("div", { style: wrapperStyle, children: [label && (_jsx("label", { style: inputStyles.label, children: label })), _jsxs("div", { style: inputStyles.container, children: [icon && (_jsx("span", { style: inputStyles.icon, children: icon })), _jsx("input", { style: inputStyle, className: className, ...props })] }), error && (_jsx("span", { style: inputStyles.error, children: error }))] }));
110
+ };
111
+ export default Input;
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ export interface LinkProps {
3
+ href: string;
4
+ children: React.ReactNode;
5
+ variant?: 'primary' | 'secondary' | 'accent';
6
+ size?: 'small' | 'medium' | 'large';
7
+ weight?: 'normal' | 'medium' | 'semibold' | 'bold';
8
+ underline?: boolean;
9
+ external?: boolean;
10
+ className?: string;
11
+ onClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void;
12
+ }
13
+ export declare const Link: React.FC<LinkProps>;
14
+ export default Link;
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export const Link = ({ href, children, variant = 'primary', size = 'medium', weight = 'normal', underline = true, external = false, className = '', onClick, }) => {
3
+ const handleClick = (e) => {
4
+ onClick?.(e);
5
+ };
6
+ return (_jsxs("a", { href: href, className: `
7
+ link
8
+ link--${variant}
9
+ link--${size}
10
+ link--${weight}
11
+ ${underline ? 'link--underline' : ''}
12
+ ${external ? 'link--external' : ''}
13
+ ${className}
14
+ `, target: external ? '_blank' : undefined, rel: external ? 'noopener noreferrer' : undefined, onClick: handleClick, children: [children, external && (_jsxs("svg", { className: "link__external-icon", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }), _jsx("polyline", { points: "15,3 21,3 21,9" }), _jsx("line", { x1: "10", y1: "14", x2: "21", y2: "3" })] }))] }));
15
+ };
16
+ export default Link;
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ export interface SelectMenuProps {
3
+ open: boolean;
4
+ anchorRef: React.RefObject<HTMLElement>;
5
+ onSelect: (option: string) => void;
6
+ onClose: () => void;
7
+ }
8
+ export declare const SelectMenu: React.FC<SelectMenuProps>;
9
+ export default SelectMenu;
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ export const SelectMenu = ({ open, anchorRef, onSelect, onClose }) => {
4
+ const menuRef = useRef(null);
5
+ const [menuStyle, setMenuStyle] = useState({});
6
+ useEffect(() => {
7
+ if (open && anchorRef.current && menuRef.current) {
8
+ const rect = anchorRef.current.getBoundingClientRect();
9
+ const menuHeight = menuRef.current.offsetHeight;
10
+ setMenuStyle({
11
+ position: 'absolute',
12
+ top: rect.top + window.scrollY - menuHeight - 8, // 8px de separación
13
+ left: rect.left + window.scrollX,
14
+ zIndex: 1000,
15
+ minWidth: 180,
16
+ });
17
+ }
18
+ }, [open, anchorRef]);
19
+ if (!open)
20
+ return null;
21
+ return (_jsx("div", { className: "select-menu__backdrop", onClick: onClose, children: _jsx("div", { className: "select-menu", ref: menuRef, style: menuStyle, onClick: e => e.stopPropagation(), children: _jsx("button", { className: "select-menu__item", onClick: () => { onSelect('attachment'); onClose(); }, children: "Agregar fotos y archivos" }) }) }));
22
+ };
23
+ export default SelectMenu;
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ export interface TextProps {
3
+ variant?: 'h1' | 'h2' | 'h3' | 'h4' | 'body' | 'small' | 'caption';
4
+ weight?: 'normal' | 'medium' | 'semibold' | 'bold';
5
+ color?: 'primary' | 'secondary' | 'inherit';
6
+ align?: 'left' | 'center' | 'right';
7
+ className?: string;
8
+ children: React.ReactNode;
9
+ }
10
+ export declare const Text: React.FC<TextProps>;
11
+ export default Text;
@@ -0,0 +1,93 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ const textStyles = {
3
+ base: {
4
+ margin: 0,
5
+ fontFamily: 'var(--font-family-base)',
6
+ },
7
+ variants: {
8
+ h1: {
9
+ fontSize: 'var(--font-size-2xl)',
10
+ lineHeight: 'var(--line-height-tight)',
11
+ fontWeight: 'var(--font-weight-bold)',
12
+ },
13
+ h2: {
14
+ fontSize: 'var(--font-size-xl)',
15
+ lineHeight: 'var(--line-height-tight)',
16
+ fontWeight: 'var(--font-weight-semibold)',
17
+ },
18
+ h3: {
19
+ fontSize: 'var(--font-size-lg)',
20
+ lineHeight: 'var(--line-height-tight)',
21
+ fontWeight: 'var(--font-weight-semibold)',
22
+ },
23
+ h4: {
24
+ fontSize: 'var(--font-size-base)',
25
+ lineHeight: 'var(--line-height-normal)',
26
+ fontWeight: 'var(--font-weight-semibold)',
27
+ },
28
+ body: {
29
+ fontSize: 'var(--font-size-base)',
30
+ lineHeight: 'var(--line-height-normal)',
31
+ },
32
+ small: {
33
+ fontSize: 'var(--font-size-sm)',
34
+ lineHeight: 'var(--line-height-normal)',
35
+ },
36
+ caption: {
37
+ fontSize: 'var(--font-size-xs)',
38
+ lineHeight: 'var(--line-height-normal)',
39
+ },
40
+ },
41
+ weights: {
42
+ normal: {
43
+ fontWeight: 'var(--font-weight-normal)',
44
+ },
45
+ medium: {
46
+ fontWeight: 'var(--font-weight-medium)',
47
+ },
48
+ semibold: {
49
+ fontWeight: 'var(--font-weight-semibold)',
50
+ },
51
+ bold: {
52
+ fontWeight: 'var(--font-weight-bold)',
53
+ },
54
+ },
55
+ colors: {
56
+ primary: {
57
+ color: 'var(--color-text)',
58
+ },
59
+ secondary: {
60
+ color: 'var(--color-text-secondary)',
61
+ },
62
+ inherit: {
63
+ color: 'inherit',
64
+ },
65
+ },
66
+ alignment: {
67
+ left: {
68
+ textAlign: 'left',
69
+ },
70
+ center: {
71
+ textAlign: 'center',
72
+ },
73
+ right: {
74
+ textAlign: 'right',
75
+ },
76
+ },
77
+ };
78
+ const getTextStyles = (props) => {
79
+ const { variant = 'body', weight = 'normal', color = 'primary', align = 'left' } = props;
80
+ return {
81
+ ...textStyles.base,
82
+ ...textStyles.variants[variant],
83
+ ...textStyles.weights[weight],
84
+ ...textStyles.colors[color],
85
+ ...textStyles.alignment[align],
86
+ };
87
+ };
88
+ export const Text = ({ variant = 'body', weight = 'normal', color = 'primary', align = 'left', className = '', children, }) => {
89
+ const Component = variant.startsWith('h') ? variant : 'p';
90
+ const textStyle = getTextStyles({ variant, weight, color, align });
91
+ return (_jsx(Component, { style: textStyle, className: className, children: children }));
92
+ };
93
+ export default Text;
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
3
+ className?: string;
4
+ }
5
+ export declare const Textarea: React.ForwardRefExoticComponent<TextareaProps & React.RefAttributes<HTMLTextAreaElement>>;
6
+ export default Textarea;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ export const Textarea = React.forwardRef(({ className = '', ...props }, ref) => (_jsx("textarea", { ref: ref, className: `textarea ${className}`, ...props })));
4
+ Textarea.displayName = 'Textarea';
5
+ export default Textarea;
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ export interface LogoProps {
3
+ className?: string;
4
+ }
5
+ export declare const Logo: React.FC<LogoProps>;
6
+ export default Logo;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export const Logo = ({ className = '' }) => {
3
+ return (_jsx("div", { className: `logo ${className}`, children: _jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: _jsx("circle", { cx: "12", cy: "12", r: "8", fill: "black" }) }) }));
4
+ };
5
+ export default Logo;
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ export interface AssistantMessageProps {
3
+ content: string;
4
+ timestamp: string;
5
+ }
6
+ export declare const AssistantMessage: React.FC<AssistantMessageProps>;
7
+ export default AssistantMessage;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import ReactMarkdown from 'react-markdown';
3
+ import { Text } from '../../../atoms/Text/Text';
4
+ import { Link } from '../../../atoms/Link/Link';
5
+ export const AssistantMessage = ({ content, timestamp, }) => {
6
+ return (_jsx("div", { className: "assistant-message", children: _jsxs("div", { className: "assistant-message__content", children: [_jsxs("div", { className: "assistant-message__bubble", children: [_jsx(Text, { variant: "caption", color: "secondary", className: "assistant-message__role", children: "Assistant" }), _jsx("div", { className: "assistant-message__markdown", children: _jsx(ReactMarkdown, { components: {
7
+ a: ({ href, children }) => (_jsx(Link, { href: href || '#', variant: "primary", size: "medium", weight: "bold", underline: false, children: children })),
8
+ }, children: content }) })] }), _jsx(Text, { variant: "caption", color: "secondary", className: "assistant-message__timestamp", children: timestamp })] }) }));
9
+ };
10
+ export default AssistantMessage;
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ export declare const AssistantThinkingIndicator: React.FC;
3
+ export default AssistantThinkingIndicator;
@@ -0,0 +1,36 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
3
+ import { Text } from '../../../atoms/Text/Text';
4
+ const messages = {
5
+ short: ["Revisando...", "Procesando...", "Analizando..."],
6
+ medium: ["Analizando tu consulta...", "Estoy viendo cómo resolverlo...", "Buscando la mejor manera de ayudarte..."],
7
+ long: ["Sigo con esto. Un momento más.", "Estoy en eso, ya te respondo.", "Ya casi lo tengo."],
8
+ veryLong: ["Esto está tardando mas de lo habitual.", "Esto lleva un poco más de tiempo.", "Lleva un poco más de lo normal."],
9
+ };
10
+ const getRandomMessage = (category) => {
11
+ const categoryMessages = messages[category];
12
+ return categoryMessages[Math.floor(Math.random() * categoryMessages.length)];
13
+ };
14
+ export const AssistantThinkingIndicator = () => {
15
+ const [displayedMessage, setDisplayedMessage] = useState(getRandomMessage('short'));
16
+ const [elapsedSeconds, setElapsedSeconds] = useState(0);
17
+ useEffect(() => {
18
+ const timer = setInterval(() => {
19
+ setElapsedSeconds(prev => prev + 1);
20
+ }, 1000);
21
+ return () => clearInterval(timer);
22
+ }, []);
23
+ useEffect(() => {
24
+ if (elapsedSeconds === 6) {
25
+ setDisplayedMessage(getRandomMessage('medium'));
26
+ }
27
+ else if (elapsedSeconds === 13) {
28
+ setDisplayedMessage(getRandomMessage('long'));
29
+ }
30
+ else if (elapsedSeconds === 21) {
31
+ setDisplayedMessage(getRandomMessage('veryLong'));
32
+ }
33
+ }, [elapsedSeconds]);
34
+ return (_jsx("div", { className: "assistant-thinking-indicator", children: _jsx("div", { className: "assistant-thinking-indicator__content", children: _jsxs("div", { className: "assistant-thinking-indicator__bubble", children: [_jsx(Text, { variant: "caption", color: "secondary", className: "assistant-thinking-indicator__role", children: "Assistant" }), _jsx("div", { className: "assistant-thinking-indicator__text-container", children: _jsx(Text, { variant: "body", color: "primary", className: "assistant-thinking-indicator__wave-text", children: displayedMessage.split('').map((char, index) => (_jsx("span", { style: { animationDelay: `${index * 0.05}s` }, children: char === ' ' ? '\u00A0' : char }, index))) }) })] }) }) }));
35
+ };
36
+ export default AssistantThinkingIndicator;
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ export interface ChatHeaderProps {
3
+ title?: string;
4
+ }
5
+ export declare const ChatHeader: React.FC<ChatHeaderProps>;
6
+ export default ChatHeader;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Logo } from '../../../atoms/logo/Logo';
3
+ export const ChatHeader = ({ title = 'Asistente' }) => {
4
+ return (_jsx("header", { className: "chat-header", children: _jsxs("div", { className: "chat-header__content", children: [_jsx(Logo, { className: "chat-header__logo" }), _jsx("span", { className: "chat-header__title", children: title })] }) }));
5
+ };
6
+ export default ChatHeader;
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ export interface ChatInputProps {
3
+ value: string;
4
+ onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
5
+ onSend: () => void;
6
+ onSendWithFiles?: (content: string, files: File[]) => void;
7
+ onInputKeyDown?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
8
+ onInputFocus?: () => void;
9
+ disabled?: boolean;
10
+ maxFiles?: number;
11
+ accept?: string;
12
+ attachments?: File[];
13
+ onAttachmentsChange?: (files: File[]) => void;
14
+ textareaRef?: React.RefObject<HTMLTextAreaElement>;
15
+ }
16
+ export declare const ChatInput: React.ForwardRefExoticComponent<ChatInputProps & React.RefAttributes<HTMLFormElement>>;
17
+ export default ChatInput;
@@ -0,0 +1,56 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef, useState, forwardRef } from 'react';
3
+ import { Button } from '../../../atoms/Button/Button';
4
+ import { Textarea } from '../../../atoms/Textarea/Textarea';
5
+ import { SelectMenu } from '../../../atoms/SelectMenu/SelectMenu';
6
+ import { AttachmentPreview } from '../../../atoms/AttachmentPreview/AttachmentPreview';
7
+ import { FullscreenViewer } from '../FullscreenViewer/FullscreenViewer';
8
+ export const ChatInput = forwardRef(({ value, onChange, onSend, onSendWithFiles, onInputKeyDown, onInputFocus, disabled = false, maxFiles = 5, accept = 'image/*,video/*,application/pdf', attachments = [], onAttachmentsChange, textareaRef, }, ref) => {
9
+ const [menuOpen, setMenuOpen] = useState(false);
10
+ const [isViewerOpen, setIsViewerOpen] = useState(false);
11
+ const [viewerIndex, setViewerIndex] = useState(null);
12
+ const moreBtnRef = useRef(null);
13
+ const fileInputRef = useRef(null);
14
+ const handleMenuSelect = (option) => {
15
+ if (option === 'attachment') {
16
+ fileInputRef.current?.click();
17
+ }
18
+ };
19
+ const handleFileChange = (e) => {
20
+ const files = e.target.files;
21
+ if (files && files.length > 0) {
22
+ const newFiles = Array.from(files).slice(0, maxFiles - attachments.length);
23
+ const updated = [...attachments, ...newFiles].slice(0, maxFiles);
24
+ onAttachmentsChange?.(updated);
25
+ e.target.value = '';
26
+ }
27
+ };
28
+ const handleRemoveAttachment = (index) => {
29
+ const updated = attachments.filter((_, i) => i !== index);
30
+ onAttachmentsChange?.(updated);
31
+ };
32
+ const handleAttachmentClick = (file) => {
33
+ const idx = attachments.findIndex(f => f === file);
34
+ setViewerIndex(idx);
35
+ setIsViewerOpen(true);
36
+ };
37
+ const handleCloseViewer = () => {
38
+ setIsViewerOpen(false);
39
+ setViewerIndex(null);
40
+ };
41
+ const handleChangeIndex = (newIndex) => {
42
+ setViewerIndex(newIndex);
43
+ };
44
+ const handleSend = () => {
45
+ if (attachments.length > 0 && onSendWithFiles) {
46
+ onSendWithFiles(value, attachments);
47
+ onAttachmentsChange?.([]);
48
+ }
49
+ else {
50
+ onSend();
51
+ }
52
+ };
53
+ return (_jsxs("form", { ref: ref, className: "chat__input", onSubmit: e => { e.preventDefault(); handleSend(); }, children: [_jsx("input", { type: "file", ref: fileInputRef, style: { display: 'none' }, multiple: maxFiles > 1, accept: accept, onChange: handleFileChange }), _jsxs("div", { className: "chat__input-stack", children: [attachments.length > 0 && (_jsxs("div", { className: "chat__attachments-wrapper", style: { position: 'relative' }, children: [_jsx("div", { className: "chat__attachments", children: attachments.map((file, idx) => (_jsx(AttachmentPreview, { file: file, onRemove: () => handleRemoveAttachment(idx), onClick: () => handleAttachmentClick(file) }, file.name + file.size + idx))) }), _jsxs("div", { className: "chat__attachments-count", children: [attachments.length, " archivo", attachments.length > 1 ? 's' : ''] })] })), _jsx(Textarea, { className: "chat__textarea", placeholder: "Type a message...", value: value, onChange: onChange, onKeyDown: onInputKeyDown, onFocus: onInputFocus, rows: 1, disabled: disabled, ref: textareaRef }), _jsxs("div", { className: "chat__actions", children: [_jsxs("div", { className: "chat__actions-left", children: [_jsx(Button, { type: "button", className: "chat__icon-btn", "aria-label": "Add file", tabIndex: -1, icon: _jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("line", { x1: "12", y1: "5", x2: "12", y2: "19" }), _jsx("line", { x1: "5", y1: "12", x2: "19", y2: "12" })] }), ref: moreBtnRef, onClick: () => setMenuOpen(true) }), _jsx(SelectMenu, { open: menuOpen, anchorRef: moreBtnRef, onSelect: handleMenuSelect, onClose: () => setMenuOpen(false) })] }), _jsxs("div", { className: "chat__actions-right", children: [_jsx(Button, { type: "button", className: "chat__icon-btn", "aria-label": "Microphone", tabIndex: -1, icon: _jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("rect", { x: "9", y: "2", width: "6", height: "12", rx: "3" }), _jsx("path", { d: "M5 10v2a7 7 0 0 0 14 0v-2" }), _jsx("line", { x1: "12", y1: "22", x2: "12", y2: "18" }), _jsx("line", { x1: "8", y1: "22", x2: "16", y2: "22" })] }) }), _jsx(Button, { variant: "circle", type: "submit", icon: _jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M12 8v8", stroke: "#fff", strokeWidth: "2", strokeLinecap: "round" }), _jsx("path", { d: "M8 12l4-4 4 4", stroke: "#fff", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" })] }), "aria-label": "Send", disabled: disabled })] })] })] }), isViewerOpen && viewerIndex !== null && attachments.length > 0 && (_jsx(FullscreenViewer, { isOpen: isViewerOpen, onClose: handleCloseViewer, files: attachments, currentIndex: viewerIndex, onChangeIndex: handleChangeIndex }))] }));
54
+ });
55
+ ChatInput.displayName = 'ChatInput';
56
+ export default ChatInput;
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ export interface FileViewerProps {
3
+ files: File[];
4
+ onClose?: () => void;
5
+ }
6
+ export declare const FileViewer: React.FC<FileViewerProps>;
7
+ export default FileViewer;
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { FullscreenViewer } from '../FullscreenViewer/FullscreenViewer';
4
+ export const FileViewer = ({ files, onClose, }) => {
5
+ const [currentFileIndex, setCurrentFileIndex] = useState(0);
6
+ const [isViewerOpen, setIsViewerOpen] = useState(false);
7
+ const currentFile = files[currentFileIndex];
8
+ const handleOpenViewer = (index) => {
9
+ setCurrentFileIndex(index);
10
+ setIsViewerOpen(true);
11
+ };
12
+ const handleCloseViewer = () => {
13
+ setIsViewerOpen(false);
14
+ onClose?.();
15
+ };
16
+ const handleChangeIndex = (newIndex) => {
17
+ setCurrentFileIndex(newIndex);
18
+ };
19
+ const handleKeyDown = (e) => {
20
+ if (e.key === 'ArrowLeft') {
21
+ handleChangeIndex((currentFileIndex - 1 + files.length) % files.length);
22
+ }
23
+ else if (e.key === 'ArrowRight') {
24
+ handleChangeIndex((currentFileIndex + 1) % files.length);
25
+ }
26
+ };
27
+ if (!files || files.length === 0)
28
+ return null;
29
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: "file-viewer", children: _jsx("div", { className: "file-viewer__grid", children: files.map((file, index) => {
30
+ const isImage = file.type.startsWith('image/');
31
+ const isVideo = file.type.startsWith('video/');
32
+ const isPdf = file.type === 'application/pdf';
33
+ return (_jsx("div", { className: "file-viewer__item", onClick: () => handleOpenViewer(index), onKeyDown: (e) => {
34
+ if (e.key === 'Enter' || e.key === ' ') {
35
+ e.preventDefault();
36
+ handleOpenViewer(index);
37
+ }
38
+ }, tabIndex: 0, role: "button", "aria-label": `Ver ${file.name} en pantalla completa`, children: isImage ? (_jsxs("div", { className: "file-viewer__image-container", children: [_jsx("img", { src: URL.createObjectURL(file), alt: file.name, className: "file-viewer__image" }), _jsx("div", { className: "file-viewer__overlay", children: _jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("circle", { cx: "11", cy: "11", r: "8" }), _jsx("path", { d: "m21 21-4.35-4.35" }), _jsx("path", { d: "M11 8v6" }), _jsx("path", { d: "M8 11h6" })] }) })] })) : (_jsxs("div", { className: "file-viewer__file-container", children: [_jsxs("div", { className: "file-viewer__file-icon", children: [isVideo && (_jsxs("svg", { width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("rect", { x: "2", y: "4", width: "20", height: "16", rx: "2" }), _jsx("polygon", { points: "10 9 15 12 10 15 10 9" })] })), isPdf && (_jsxs("div", { className: "file-viewer__pdf-icon", children: [_jsxs("svg", { width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }), _jsx("polyline", { points: "14,2 14,8 20,8" }), _jsx("line", { x1: "16", y1: "13", x2: "8", y2: "13" }), _jsx("line", { x1: "16", y1: "17", x2: "8", y2: "17" }), _jsx("polyline", { points: "10,9 9,9 8,9" })] }), _jsx("span", { children: "PDF" })] })), !isVideo && !isPdf && (_jsxs("svg", { width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }), _jsx("polyline", { points: "14,2 14,8 20,8" }), _jsx("line", { x1: "16", y1: "13", x2: "8", y2: "13" }), _jsx("line", { x1: "16", y1: "17", x2: "8", y2: "17" }), _jsx("polyline", { points: "10,9 9,9 8,9" })] }))] }), _jsxs("div", { className: "file-viewer__file-info", children: [_jsx("div", { className: "file-viewer__file-name", children: file.name }), _jsx("div", { className: "file-viewer__file-type", children: file.type || 'Archivo' })] })] })) }, `${file.name}-${file.size}-${index}`));
39
+ }) }) }), isViewerOpen && currentFile && (_jsx("div", { onKeyDown: handleKeyDown, tabIndex: -1, children: _jsx(FullscreenViewer, { isOpen: isViewerOpen, onClose: handleCloseViewer, files: files, currentIndex: currentFileIndex, onChangeIndex: handleChangeIndex }) }))] }));
40
+ };
41
+ export default FileViewer;
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ export interface FullscreenViewerProps {
3
+ isOpen: boolean;
4
+ onClose: () => void;
5
+ files: File[];
6
+ currentIndex: number;
7
+ onChangeIndex: (newIndex: number) => void;
8
+ }
9
+ export declare const FullscreenViewer: React.FC<FullscreenViewerProps>;
10
+ export default FullscreenViewer;
@@ -0,0 +1,65 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
3
+ import { Modal } from '../Modal/Modal';
4
+ import { Button } from '../../../atoms/Button/Button';
5
+ export const FullscreenViewer = ({ isOpen, onClose, files, currentIndex, onChangeIndex, }) => {
6
+ const file = files[currentIndex];
7
+ const [imageLoaded, setImageLoaded] = useState(false);
8
+ const [imageError, setImageError] = useState(false);
9
+ useEffect(() => {
10
+ setImageLoaded(false);
11
+ setImageError(false);
12
+ }, [file]);
13
+ const isImage = file.type.startsWith('image/');
14
+ const isVideo = file.type.startsWith('video/');
15
+ const isPdf = file.type === 'application/pdf';
16
+ const formatSize = (size) => {
17
+ if (size < 1024)
18
+ return `${size} B`;
19
+ if (size < 1024 * 1024)
20
+ return `${(size / 1024).toFixed(1)} KB`;
21
+ return `${(size / (1024 * 1024)).toFixed(1)} MB`;
22
+ };
23
+ const getFileIcon = () => {
24
+ if (isVideo) {
25
+ return (_jsxs("svg", { width: "64", height: "64", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("rect", { x: "2", y: "4", width: "20", height: "16", rx: "2" }), _jsx("polygon", { points: "10 9 15 12 10 15 10 9" })] }));
26
+ }
27
+ if (isPdf) {
28
+ return (_jsxs("div", { className: "fullscreen-viewer__pdf-icon", children: [_jsxs("svg", { width: "64", height: "64", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }), _jsx("polyline", { points: "14,2 14,8 20,8" }), _jsx("line", { x1: "16", y1: "13", x2: "8", y2: "13" }), _jsx("line", { x1: "16", y1: "17", x2: "8", y2: "17" }), _jsx("polyline", { points: "10,9 9,9 8,9" })] }), _jsx("span", { children: "PDF" })] }));
29
+ }
30
+ return (_jsxs("svg", { width: "64", height: "64", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }), _jsx("polyline", { points: "14,2 14,8 20,8" }), _jsx("line", { x1: "16", y1: "13", x2: "8", y2: "13" }), _jsx("line", { x1: "16", y1: "17", x2: "8", y2: "17" }), _jsx("polyline", { points: "10,9 9,9 8,9" })] }));
31
+ };
32
+ const handleDownload = () => {
33
+ const url = URL.createObjectURL(file);
34
+ const a = document.createElement('a');
35
+ a.href = url;
36
+ a.download = file.name;
37
+ document.body.appendChild(a);
38
+ a.click();
39
+ document.body.removeChild(a);
40
+ URL.revokeObjectURL(url);
41
+ };
42
+ const handlePrevious = () => {
43
+ onChangeIndex((currentIndex - 1 + files.length) % files.length);
44
+ };
45
+ const handleNext = () => {
46
+ onChangeIndex((currentIndex + 1) % files.length);
47
+ };
48
+ const handleKeyDown = (e) => {
49
+ if (e.key === 'ArrowLeft')
50
+ handlePrevious();
51
+ if (e.key === 'ArrowRight')
52
+ handleNext();
53
+ };
54
+ const renderContent = () => {
55
+ if (isImage) {
56
+ return (_jsxs("div", { className: "fullscreen-viewer__image-container", children: [!imageLoaded && !imageError && (_jsxs("div", { className: "fullscreen-viewer__loading", children: [_jsx("div", { className: "fullscreen-viewer__spinner" }), _jsx("p", { children: "Cargando imagen..." })] })), imageError && (_jsxs("div", { className: "fullscreen-viewer__error", children: [_jsxs("svg", { width: "48", height: "48", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("circle", { cx: "12", cy: "12", r: "10" }), _jsx("line", { x1: "15", y1: "9", x2: "9", y2: "15" }), _jsx("line", { x1: "9", y1: "9", x2: "15", y2: "15" })] }), _jsx("p", { children: "Error al cargar la imagen" })] })), _jsx("img", { src: URL.createObjectURL(file), alt: file.name, className: `fullscreen-viewer__image ${imageLoaded ? 'fullscreen-viewer__image--loaded' : ''}`, onLoad: () => setImageLoaded(true), onError: () => setImageError(true), style: { display: imageError ? 'none' : 'block' } })] }));
57
+ }
58
+ if (isVideo) {
59
+ return (_jsx("div", { className: "fullscreen-viewer__video-container", children: _jsx("video", { controls: true, className: "fullscreen-viewer__video", src: URL.createObjectURL(file), children: "Tu navegador no soporta el elemento de video." }) }));
60
+ }
61
+ return (_jsxs("div", { className: "fullscreen-viewer__file-container", children: [_jsx("div", { className: "fullscreen-viewer__file-icon", children: getFileIcon() }), _jsxs("div", { className: "fullscreen-viewer__file-info", children: [_jsx("h3", { className: "fullscreen-viewer__file-name", children: file.name }), _jsxs("p", { className: "fullscreen-viewer__file-details", children: [file.type || 'Archivo', " \u00B7 ", formatSize(file.size)] }), _jsx(Button, { onClick: handleDownload, className: "fullscreen-viewer__download-btn", icon: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }), _jsx("polyline", { points: "7,10 12,15 17,10" }), _jsx("line", { x1: "12", y1: "15", x2: "12", y2: "3" })] }), children: "Descargar" })] })] }));
62
+ };
63
+ return (_jsx(Modal, { isOpen: isOpen, onClose: onClose, title: undefined, showCloseButton: false, closeOnOverlayClick: true, closeOnEscape: true, children: _jsxs("div", { className: "fullscreen-viewer", onKeyDown: handleKeyDown, tabIndex: 0, children: [_jsxs("div", { className: "fullscreen-viewer__floating-header", children: [_jsx("span", { className: "fullscreen-viewer__floating-title", children: file.name }), _jsx("button", { className: "fullscreen-viewer__floating-close", onClick: onClose, "aria-label": "Cerrar visor", type: "button", children: _jsxs("svg", { width: "28", height: "28", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), _jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }) })] }), files.length > 1 && (_jsxs(_Fragment, { children: [_jsx(Button, { className: "fullscreen-viewer__arrow fullscreen-viewer__arrow--left", variant: "circle", type: "button", onClick: handlePrevious, "aria-label": "Archivo anterior", icon: _jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("polyline", { points: "15,18 9,12 15,6" }) }) }), _jsx(Button, { className: "fullscreen-viewer__arrow fullscreen-viewer__arrow--right", variant: "circle", type: "button", onClick: handleNext, "aria-label": "Archivo siguiente", icon: _jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("polyline", { points: "9,18 15,12 9,6" }) }) })] })), renderContent(), files.length > 1 && (_jsxs("div", { className: "fullscreen-viewer__counter", children: [currentIndex + 1, " de ", files.length] }))] }) }));
64
+ };
65
+ export default FullscreenViewer;
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ export type MessageStatusType = 'sending' | 'sent' | 'failed';
3
+ interface MessageStatusProps {
4
+ status: MessageStatusType;
5
+ className?: string;
6
+ onRetry?: () => void;
7
+ }
8
+ export declare const MessageStatus: React.FC<MessageStatusProps>;
9
+ export default MessageStatus;