@sybilion/uilib 1.3.11 → 1.3.12

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.
@@ -77,7 +77,7 @@ function ChatChrome({ showResizeHandle, resizeHandle, onClose, isEmpty, renderPr
77
77
  })
78
78
  : null, isScriptComplete &&
79
79
  onGenerateDashboard &&
80
- !generatingDashboard ? (jsxs(Button, { type: "button", variant: "default", size: "lg", disabled: isLoading, onClick: onGenerateDashboardClick, children: [jsx(ChartLineIcon, {}), "Generate Dashboard"] })) : null] })), showInlinePresets && renderPresets('inline'), isLoading && isLastMessageFromUser && (jsx(TextShimmer, { duration: 1, spread: 5, className: S.loader, children: "Thinking..." }))] }) })), jsxs("div", { className: cn(S.footer, footerClassName), children: [isEmpty ? (jsx("div", { className: S.notice, children: "Forecast Assistant can make mistakes." })) : null, jsx(Chat.Prompt, { onSubmit: handlePromptSubmitWithAttachments, disabled: promptBusy, attachments: pendingAttachments, onRemoveAttachment: handleRemoveAttachment, prefillMessage: promptPrefill ?? undefined })] })] })] })] }));
80
+ !generatingDashboard ? (jsxs(Button, { type: "button", variant: "default", size: "lg", disabled: isLoading, onClick: onGenerateDashboardClick, children: [jsx(ChartLineIcon, {}), "Generate Dashboard"] })) : null] })), showInlinePresets && renderPresets('inline'), isLoading && isLastMessageFromUser && (jsx(TextShimmer, { duration: 1, spread: 5, className: S.loader, children: "Thinking..." }))] }) })), jsxs("div", { className: cn(S.footer, footerClassName), children: [isEmpty ? (jsx("div", { className: S.notice, children: "Forecast Assistant can make mistakes." })) : null, jsx(Chat.Prompt, { onSubmit: handlePromptSubmitWithAttachments, disabled: promptBusy, attachments: pendingAttachments, onRemoveAttachment: handleRemoveAttachment, prefillMessage: promptPrefill ?? undefined, attachmentAccept: attachmentsDropzoneEnabled ? attachmentAccept : undefined, onAttachmentFiles: attachmentsDropzoneEnabled ? handleAttachmentFiles : undefined })] })] })] })] }));
81
81
  }
82
82
 
83
83
  export { ChatChrome };
@@ -1,17 +1,19 @@
1
- import { jsxs, jsx } from 'react/jsx-runtime';
1
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
2
  import cn from 'classnames';
3
3
  import { useState, useRef, useLayoutEffect, useEffect } from 'react';
4
4
  import useEvent from '../../../../hooks/useEvent.js';
5
- import { SendHorizontalIcon } from 'lucide-react';
5
+ import { PaperclipIcon, SendHorizontalIcon } from 'lucide-react';
6
6
  import { Button } from '../../Button/Button.js';
7
7
  import { Input } from '../../Input/Input.js';
8
8
  import { syncChatPromptTextareaHeight } from './ChatPrompt.helpers.js';
9
9
  import S from './ChatPrompt.styl.js';
10
10
  import { ChatPromptAttachments } from './ChatPromptAttachments.js';
11
11
 
12
- function ChatPrompt({ onSubmit, placeholder, className, footer, prefillMessage, attachments = [], onRemoveAttachment, disabled = false, }) {
12
+ function ChatPrompt({ onSubmit, placeholder, className, footer, prefillMessage, attachments = [], onRemoveAttachment, disabled = false, attachmentAccept, onAttachmentFiles, }) {
13
13
  const [message, setMessage] = useState('');
14
14
  const inputRef = useRef(null);
15
+ const fileInputRef = useRef(null);
16
+ const showAttachButton = Boolean(attachmentAccept && onAttachmentFiles);
15
17
  useLayoutEffect(() => {
16
18
  const el = inputRef.current;
17
19
  if (!el)
@@ -32,6 +34,13 @@ function ChatPrompt({ onSubmit, placeholder, className, footer, prefillMessage,
32
34
  setMessage('');
33
35
  }
34
36
  };
37
+ const handleFileInputChange = (e) => {
38
+ const files = Array.from(e.target.files ?? []);
39
+ e.target.value = '';
40
+ if (files.length > 0) {
41
+ onAttachmentFiles?.(files);
42
+ }
43
+ };
35
44
  useEvent({
36
45
  event: 'keydown',
37
46
  callback: (e) => {
@@ -42,7 +51,10 @@ function ChatPrompt({ onSubmit, placeholder, className, footer, prefillMessage,
42
51
  }
43
52
  },
44
53
  });
45
- return (jsxs("form", { onSubmit: handleSubmit, className: cn(S.root, className), children: [jsx(ChatPromptAttachments, { attachments: attachments, onRemove: index => onRemoveAttachment?.(index), disabled: disabled }), jsxs("div", { className: S.composer, children: [jsx(Input, { ref: inputRef, type: "textarea", rows: 1, value: message, onChange: e => setMessage(e.target.value), placeholder: placeholder || 'Type a message...', className: cn(S.input) }), jsx("div", { className: S.submitColumn, children: jsx(Button, { type: "submit", size: "sm", disabled: disabled || (!message.trim() && attachments.length === 0), children: jsx(SendHorizontalIcon, { size: 16 }) }) })] }), footer] }));
54
+ return (jsxs("form", { onSubmit: handleSubmit, className: cn(S.root, className), children: [jsx(ChatPromptAttachments, { attachments: attachments, onRemove: index => onRemoveAttachment?.(index), disabled: disabled }), jsxs("div", { className: S.composer, children: [showAttachButton ? (jsxs(Fragment, { children: [jsx("input", { ref: fileInputRef, type: "file", accept: attachmentAccept, multiple: true, className: S.fileInput, disabled: disabled, onChange: handleFileInputChange }), jsx(Button, { type: "button", variant: "ghost", icon: true, size: "sm", className: S.attachButton, "aria-label": "Attach file", disabled: disabled, onClick: e => {
55
+ e.preventDefault();
56
+ fileInputRef.current?.click();
57
+ }, children: jsx(PaperclipIcon, { size: 16 }) })] })) : null, jsx(Input, { ref: inputRef, type: "textarea", rows: 1, value: message, onChange: e => setMessage(e.target.value), placeholder: placeholder || 'Type a message...', className: cn(S.input) }), jsx("div", { className: S.submitColumn, children: jsx(Button, { type: "submit", size: "sm", disabled: disabled || (!message.trim() && attachments.length === 0), children: jsx(SendHorizontalIcon, { size: 16 }) }) })] }), footer] }));
46
58
  }
47
59
 
48
60
  export { ChatPrompt };
@@ -1,7 +1,7 @@
1
1
  import styleInject from 'style-inject';
2
2
 
3
- var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.ChatPrompt_root__5G5bq{align-items:stretch;display:flex;flex-direction:column;gap:var(--p-2);padding:var(--p-6);padding-top:var(--p-5);position:relative}.ChatPrompt_composer__H3c3N{align-items:flex-end;display:flex;flex-direction:row;gap:var(--p-3);position:relative;width:100%}.ChatPrompt_input__QPKBV{border:none;border-radius:0!important;flex:1;max-height:200px;min-height:40px;min-width:0;overflow-y:auto;padding:var(--p-2) 0 0!important;resize:none}.ChatPrompt_input__QPKBV,.ChatPrompt_input__QPKBV:focus{box-shadow:none!important}.ChatPrompt_submitColumn__0rY1R{align-items:center;display:flex;flex-direction:column;flex-shrink:0;justify-content:flex-end}.ChatPrompt_submitColumn__0rY1R>button:focus{box-shadow:0 0 0 2px var(--brand-color-500)!important}.ChatPrompt_submitColumn__0rY1R>button:first-child{border:none;position:relative;transition:all .2s}.ChatPrompt_submitColumn__0rY1R>button:first-child:focus{transform:scale(1.2)}.ChatPrompt_submitColumn__0rY1R>button:first-child:before{bottom:-100%;content:\"\";left:-100%;position:absolute;right:-100%;top:-100%}.ChatPrompt_submitColumn__0rY1R .ChatPrompt_attachButton__gi-qF{background-color:var(--page-color);box-shadow:0 0 20px var(--background)}.ChatPrompt_attachments__KG-fG{display:flex;flex-wrap:wrap;gap:var(--p-2);margin-bottom:var(--p-2)}.ChatPrompt_attachmentItem__QJk7J{flex:1 1 300px;max-width:300px;min-width:0}";
4
- var S = {"root":"ChatPrompt_root__5G5bq","composer":"ChatPrompt_composer__H3c3N","input":"ChatPrompt_input__QPKBV","submitColumn":"ChatPrompt_submitColumn__0rY1R","attachButton":"ChatPrompt_attachButton__gi-qF","attachments":"ChatPrompt_attachments__KG-fG","attachmentItem":"ChatPrompt_attachmentItem__QJk7J"};
3
+ var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.ChatPrompt_root__5G5bq{align-items:stretch;display:flex;flex-direction:column;gap:var(--p-2);padding:var(--p-6);padding-top:var(--p-5);position:relative}.ChatPrompt_composer__H3c3N{align-items:flex-end;display:flex;flex-direction:row;gap:var(--p-3);position:relative;width:100%}.ChatPrompt_fileInput__xdgPn{display:none}.ChatPrompt_attachButton__gi-qF{align-self:flex-end;flex-shrink:0}.ChatPrompt_input__QPKBV{border:none;border-radius:0!important;flex:1;max-height:200px;min-height:40px;min-width:0;overflow-y:auto;padding:var(--p-2) 0 0!important;resize:none}.ChatPrompt_input__QPKBV,.ChatPrompt_input__QPKBV:focus{box-shadow:none!important}.ChatPrompt_submitColumn__0rY1R{align-items:center;display:flex;flex-direction:column;flex-shrink:0;justify-content:flex-end}.ChatPrompt_submitColumn__0rY1R>button:focus{box-shadow:0 0 0 2px var(--brand-color-500)!important}.ChatPrompt_submitColumn__0rY1R>button:first-child{border:none;position:relative;transition:all .2s}.ChatPrompt_submitColumn__0rY1R>button:first-child:focus{transform:scale(1.2)}.ChatPrompt_submitColumn__0rY1R>button:first-child:before{bottom:-100%;content:\"\";left:-100%;position:absolute;right:-100%;top:-100%}.ChatPrompt_attachments__KG-fG{display:flex;flex-wrap:wrap;gap:var(--p-2);margin-bottom:var(--p-2)}.ChatPrompt_attachmentItem__QJk7J{flex:1 1 300px;max-width:300px;min-width:0}";
4
+ var S = {"root":"ChatPrompt_root__5G5bq","composer":"ChatPrompt_composer__H3c3N","fileInput":"ChatPrompt_fileInput__xdgPn","attachButton":"ChatPrompt_attachButton__gi-qF","input":"ChatPrompt_input__QPKBV","submitColumn":"ChatPrompt_submitColumn__0rY1R","attachments":"ChatPrompt_attachments__KG-fG","attachmentItem":"ChatPrompt_attachmentItem__QJk7J"};
5
5
  styleInject(css_248z);
6
6
 
7
7
  export { S as default };
@@ -77,6 +77,10 @@ export interface ChatPromptProps {
77
77
  /** Staged files shown above the composer until send. */
78
78
  attachments?: ChatAttachmentDropItem[];
79
79
  onRemoveAttachment?: (index: number) => void;
80
+ /** HTML `accept` for the attach file picker; set with `onAttachmentFiles`. */
81
+ attachmentAccept?: string;
82
+ /** Called when the user picks files via the attach button. */
83
+ onAttachmentFiles?: (files: File[]) => void;
80
84
  }
81
85
  export interface ChatMessageProps {
82
86
  role: MessageRole;
@@ -1,2 +1,2 @@
1
1
  import type { ChatPromptProps } from '../Chat.types';
2
- export declare function ChatPrompt({ onSubmit, placeholder, className, footer, prefillMessage, attachments, onRemoveAttachment, disabled, }: ChatPromptProps): import("react/jsx-runtime").JSX.Element;
2
+ export declare function ChatPrompt({ onSubmit, placeholder, className, footer, prefillMessage, attachments, onRemoveAttachment, disabled, attachmentAccept, onAttachmentFiles, }: ChatPromptProps): import("react/jsx-runtime").JSX.Element;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sybilion/uilib",
3
- "version": "1.3.11",
3
+ "version": "1.3.12",
4
4
  "description": "Sybilion Design System — React UI components (Webpack + Stylus)",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -88,6 +88,10 @@ export interface ChatPromptProps {
88
88
  /** Staged files shown above the composer until send. */
89
89
  attachments?: ChatAttachmentDropItem[];
90
90
  onRemoveAttachment?: (index: number) => void;
91
+ /** HTML `accept` for the attach file picker; set with `onAttachmentFiles`. */
92
+ attachmentAccept?: string;
93
+ /** Called when the user picks files via the attach button. */
94
+ onAttachmentFiles?: (files: File[]) => void;
91
95
  }
92
96
 
93
97
  export interface ChatMessageProps {
@@ -284,6 +284,12 @@ export function ChatChrome({
284
284
  attachments={pendingAttachments}
285
285
  onRemoveAttachment={handleRemoveAttachment}
286
286
  prefillMessage={promptPrefill ?? undefined}
287
+ attachmentAccept={
288
+ attachmentsDropzoneEnabled ? attachmentAccept : undefined
289
+ }
290
+ onAttachmentFiles={
291
+ attachmentsDropzoneEnabled ? handleAttachmentFiles : undefined
292
+ }
287
293
  />
288
294
  </div>
289
295
  </Chat>
@@ -19,6 +19,13 @@ INPUT_MAX_HEIGHT = 200px
19
19
  gap var(--p-3)
20
20
  width 100%
21
21
 
22
+ .fileInput
23
+ display none
24
+
25
+ .attachButton
26
+ flex-shrink 0
27
+ align-self flex-end
28
+
22
29
  .input
23
30
  flex 1
24
31
  min-width 0
@@ -61,10 +68,6 @@ INPUT_MAX_HEIGHT = 200px
61
68
  right -100%
62
69
  bottom -100%
63
70
 
64
- .attachButton
65
- background-color var(--page-color)
66
- box-shadow 0 0 20px var(--background)
67
-
68
71
  .attachments
69
72
  display flex
70
73
  flex-wrap wrap
@@ -5,6 +5,7 @@ interface CssExports {
5
5
  'attachmentItem': string;
6
6
  'attachments': string;
7
7
  'composer': string;
8
+ 'fileInput': string;
8
9
  'input': string;
9
10
  'root': string;
10
11
  'submitColumn': string;
@@ -1,8 +1,15 @@
1
1
  import cn from 'classnames';
2
- import { FormEvent, useEffect, useLayoutEffect, useRef, useState } from 'react';
2
+ import {
3
+ ChangeEvent,
4
+ FormEvent,
5
+ useEffect,
6
+ useLayoutEffect,
7
+ useRef,
8
+ useState,
9
+ } from 'react';
3
10
 
4
11
  import useEvent from '#uilib/hooks/useEvent';
5
- import { SendHorizontalIcon } from 'lucide-react';
12
+ import { PaperclipIcon, SendHorizontalIcon } from 'lucide-react';
6
13
 
7
14
  import { Button } from '../../Button';
8
15
  import { Input } from '../../Input';
@@ -20,9 +27,13 @@ export function ChatPrompt({
20
27
  attachments = [],
21
28
  onRemoveAttachment,
22
29
  disabled = false,
30
+ attachmentAccept,
31
+ onAttachmentFiles,
23
32
  }: ChatPromptProps) {
24
33
  const [message, setMessage] = useState('');
25
34
  const inputRef = useRef<HTMLTextAreaElement>(null);
35
+ const fileInputRef = useRef<HTMLInputElement>(null);
36
+ const showAttachButton = Boolean(attachmentAccept && onAttachmentFiles);
26
37
 
27
38
  useLayoutEffect(() => {
28
39
  const el = inputRef.current;
@@ -47,6 +58,14 @@ export function ChatPrompt({
47
58
  }
48
59
  };
49
60
 
61
+ const handleFileInputChange = (e: ChangeEvent<HTMLInputElement>) => {
62
+ const files = Array.from(e.target.files ?? []);
63
+ e.target.value = '';
64
+ if (files.length > 0) {
65
+ onAttachmentFiles?.(files);
66
+ }
67
+ };
68
+
50
69
  useEvent({
51
70
  event: 'keydown',
52
71
  callback: (e: KeyboardEvent) => {
@@ -66,6 +85,35 @@ export function ChatPrompt({
66
85
  disabled={disabled}
67
86
  />
68
87
  <div className={S.composer}>
88
+ {showAttachButton ? (
89
+ <>
90
+ <input
91
+ ref={fileInputRef}
92
+ type="file"
93
+ accept={attachmentAccept}
94
+ multiple
95
+ className={S.fileInput}
96
+ disabled={disabled}
97
+ onChange={handleFileInputChange}
98
+ />
99
+ <Button
100
+ type="button"
101
+ variant="ghost"
102
+ icon
103
+ size="sm"
104
+ className={S.attachButton}
105
+ aria-label="Attach file"
106
+ disabled={disabled}
107
+ onClick={e => {
108
+ e.preventDefault();
109
+ fileInputRef.current?.click();
110
+ }}
111
+ >
112
+ <PaperclipIcon size={16} />
113
+ </Button>
114
+ </>
115
+ ) : null}
116
+
69
117
  <Input
70
118
  ref={inputRef}
71
119
  type="textarea"
@@ -85,15 +133,6 @@ export function ChatPrompt({
85
133
  <SendHorizontalIcon size={16} />
86
134
  </Button>
87
135
  </div>
88
-
89
- {/* <Button
90
- variant="outline"
91
- size="sm"
92
- onClick={e => e.preventDefault()}
93
- className={S.attachButton}
94
- >
95
- <PaperclipIcon size={16} />
96
- </Button> */}
97
136
  </div>
98
137
 
99
138
  {footer}