@sybilion/uilib 1.3.9 → 1.3.11

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 (74) hide show
  1. package/dist/esm/components/ui/ChartAreaInteractive/ChartAreaInteractive.js +4 -2
  2. package/dist/esm/components/ui/Chat/ChatChrome/ChatChrome.js +52 -17
  3. package/dist/esm/components/ui/Chat/ChatChrome/ChatChrome.styl.js +2 -2
  4. package/dist/esm/components/ui/Chat/ChatMessage/ChatMessage.styl.js +2 -2
  5. package/dist/esm/components/ui/Chat/ChatMessage/UserCsvAttachmentBubble.js +3 -4
  6. package/dist/esm/components/ui/Chat/ChatPrompt/ChatPrompt.js +6 -4
  7. package/dist/esm/components/ui/Chat/ChatPrompt/ChatPrompt.styl.js +2 -2
  8. package/dist/esm/components/ui/Chat/ChatPrompt/ChatPromptAttachments.js +11 -0
  9. package/dist/esm/components/ui/Chat/ChatSheet/ChatSelector.js +1 -1
  10. package/dist/esm/components/ui/Chat/ChatSheet/useChatPanelChromeModel.js +4 -1
  11. package/dist/esm/components/ui/Chat/chatAttachmentAccept.js +54 -0
  12. package/dist/esm/components/ui/Chat/chatAttachmentExtract.js +26 -0
  13. package/dist/esm/components/ui/Chat/chatPdfExtract.js +31 -0
  14. package/dist/esm/components/ui/DropZone/DropZone.js +50 -21
  15. package/dist/esm/components/ui/DropZone/DropZone.styl.js +2 -2
  16. package/dist/esm/components/ui/FileChip/FileChip.js +26 -0
  17. package/dist/esm/components/ui/FileChip/FileChip.styl.js +7 -0
  18. package/dist/esm/index.js +2 -0
  19. package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.d.ts +1 -1
  20. package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.types.d.ts +2 -0
  21. package/dist/esm/types/src/components/ui/Chat/Chat.types.d.ts +10 -1
  22. package/dist/esm/types/src/components/ui/Chat/ChatChrome/ChatChrome.d.ts +1 -1
  23. package/dist/esm/types/src/components/ui/Chat/ChatChrome/ChatChrome.types.d.ts +9 -2
  24. package/dist/esm/types/src/components/ui/Chat/ChatChrome/index.d.ts +1 -1
  25. package/dist/esm/types/src/components/ui/Chat/ChatPrompt/ChatPrompt.d.ts +1 -1
  26. package/dist/esm/types/src/components/ui/Chat/ChatPrompt/ChatPromptAttachments.d.ts +8 -0
  27. package/dist/esm/types/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.d.ts +7 -1
  28. package/dist/esm/types/src/components/ui/Chat/chatAttachmentAccept.d.ts +8 -0
  29. package/dist/esm/types/src/components/ui/Chat/chatAttachmentExtract.d.ts +2 -0
  30. package/dist/esm/types/src/components/ui/Chat/chatPdfExtract.d.ts +2 -0
  31. package/dist/esm/types/src/components/ui/Chat/index.d.ts +2 -1
  32. package/dist/esm/types/src/components/ui/DropZone/DropZone.d.ts +2 -0
  33. package/dist/esm/types/src/components/ui/FileChip/FileChip.d.ts +2 -0
  34. package/dist/esm/types/src/components/ui/FileChip/FileChip.types.d.ts +10 -0
  35. package/dist/esm/types/src/components/ui/FileChip/index.d.ts +2 -0
  36. package/dist/esm/types/src/docs/pages/ChatAttachmentsDropzonePage.d.ts +1 -0
  37. package/dist/esm/types/src/docs/pages/FileChipPage.d.ts +1 -0
  38. package/dist/esm/types/src/index.d.ts +1 -0
  39. package/package.json +2 -1
  40. package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.tsx +4 -1
  41. package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.types.ts +2 -0
  42. package/src/components/ui/Chat/Chat.types.ts +11 -1
  43. package/src/components/ui/Chat/ChatChrome/ChatChrome.styl +20 -0
  44. package/src/components/ui/Chat/ChatChrome/ChatChrome.styl.d.ts +2 -0
  45. package/src/components/ui/Chat/ChatChrome/ChatChrome.tsx +88 -4
  46. package/src/components/ui/Chat/ChatChrome/ChatChrome.types.ts +18 -2
  47. package/src/components/ui/Chat/ChatChrome/index.ts +1 -0
  48. package/src/components/ui/Chat/ChatMessage/ChatMessage.styl +0 -56
  49. package/src/components/ui/Chat/ChatMessage/ChatMessage.styl.d.ts +0 -5
  50. package/src/components/ui/Chat/ChatMessage/UserCsvAttachmentBubble.tsx +6 -15
  51. package/src/components/ui/Chat/ChatPrompt/ChatPrompt.styl +11 -15
  52. package/src/components/ui/Chat/ChatPrompt/ChatPrompt.styl.d.ts +2 -1
  53. package/src/components/ui/Chat/ChatPrompt/ChatPrompt.tsx +17 -8
  54. package/src/components/ui/Chat/ChatPrompt/ChatPromptAttachments.tsx +34 -0
  55. package/src/components/ui/Chat/ChatSheet/ChatSelector.tsx +12 -11
  56. package/src/components/ui/Chat/ChatSheet/ChatSheet.styl.d.ts +13 -13
  57. package/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.tsx +14 -0
  58. package/src/components/ui/Chat/chat-preset-utils.ts +4 -1
  59. package/src/components/ui/Chat/chatAttachmentAccept.ts +70 -0
  60. package/src/components/ui/Chat/chatAttachmentExtract.ts +33 -0
  61. package/src/components/ui/Chat/chatPdfExtract.ts +37 -0
  62. package/src/components/ui/Chat/index.ts +5 -0
  63. package/src/components/ui/DropZone/DropZone.styl +24 -0
  64. package/src/components/ui/DropZone/DropZone.styl.d.ts +3 -0
  65. package/src/components/ui/DropZone/DropZone.tsx +77 -24
  66. package/src/components/ui/FileChip/FileChip.styl +108 -0
  67. package/src/components/ui/FileChip/FileChip.styl.d.ts +12 -0
  68. package/src/components/ui/FileChip/FileChip.tsx +93 -0
  69. package/src/components/ui/FileChip/FileChip.types.ts +11 -0
  70. package/src/components/ui/FileChip/index.ts +2 -0
  71. package/src/docs/pages/ChatAttachmentsDropzonePage.tsx +162 -0
  72. package/src/docs/pages/FileChipPage.tsx +50 -0
  73. package/src/docs/registry.ts +12 -0
  74. package/src/index.ts +1 -0
@@ -0,0 +1,108 @@
1
+ @import 'lib/theme.styl'
2
+
3
+ .root
4
+ appearance none
5
+ border 0
6
+ margin 0
7
+ font inherit
8
+ display flex
9
+ align-items stretch
10
+ width fit-content
11
+ max-width 100%
12
+ text-align left
13
+ padding var(--p-3)
14
+ padding-right var(--p-4)
15
+ background-color var(--background)
16
+ box-shadow 0 0 0 1px var(--border)
17
+ border-radius var(--p-3)
18
+ color inherit
19
+
20
+ :global(.dark) &
21
+ background-color var(--sb-gray-800)
22
+
23
+ .header
24
+ display flex
25
+ flex-direction row
26
+ align-items center
27
+ gap var(--p-2)
28
+ width 100%
29
+ min-width 0
30
+ font-size inherit
31
+
32
+ :global([data-slot="card-action"])
33
+ grid-column auto
34
+ grid-row auto
35
+ align-self center
36
+ margin-left auto
37
+ flex-shrink 0
38
+ order 3
39
+
40
+ // CardHeader: icon wrapper, then action children, then headerText
41
+ & > div:first-child
42
+ order 1
43
+ display flex
44
+ align-items center
45
+ justify-content center
46
+ width 32px
47
+ height 32px
48
+ min-width 32px
49
+ flex-shrink 0
50
+ border-radius 0
51
+ box-shadow none
52
+ background transparent
53
+
54
+ & > svg
55
+ width 32px
56
+ height 32px
57
+
58
+ & > div:last-child
59
+ order 2
60
+ flex 1
61
+ min-width 0
62
+
63
+ .interactive
64
+ cursor pointer
65
+ transition background-color 150ms
66
+
67
+ &:hover
68
+ background-color var(--sb-gray-50)
69
+
70
+ &:focus-visible
71
+ outline 2px solid var(--ring)
72
+ outline-offset 2px
73
+
74
+ :global(.dark) &
75
+ &:hover
76
+ background-color var(--sb-gray-900)
77
+
78
+ .title
79
+ font-size var(--text-sm)
80
+ font-weight 500
81
+ line-height 1.4
82
+ white-space nowrap
83
+ overflow hidden
84
+ text-overflow ellipsis
85
+
86
+ .hint
87
+ font-size var(--text-xs)
88
+ line-height 1.4
89
+
90
+ .remove
91
+ display inline-flex
92
+ align-items center
93
+ justify-content center
94
+ width 28px
95
+ height 28px
96
+ border none
97
+ border-radius var(--p-2)
98
+ background transparent
99
+ color var(--muted-foreground)
100
+ cursor pointer
101
+
102
+ &:hover:not(:disabled)
103
+ background-color var(--muted)
104
+ color var(--foreground)
105
+
106
+ &:disabled
107
+ opacity 0.5
108
+ cursor not-allowed
@@ -0,0 +1,12 @@
1
+ // This file is automatically generated.
2
+ // Please do not change this file!
3
+ interface CssExports {
4
+ 'header': string;
5
+ 'hint': string;
6
+ 'interactive': string;
7
+ 'remove': string;
8
+ 'root': string;
9
+ 'title': string;
10
+ }
11
+ export const cssExports: CssExports;
12
+ export default cssExports;
@@ -0,0 +1,93 @@
1
+ import cn from 'classnames';
2
+
3
+ import { CsvIcon } from '#uilib/components/icons/CsvIcon/CsvIcon';
4
+ import { CardAction, CardHeader } from '#uilib/components/ui/Card';
5
+ import { FileTextIcon, XIcon } from '@phosphor-icons/react';
6
+
7
+ import S from './FileChip.styl';
8
+ import type { FileChipFormat, FileChipProps } from './FileChip.types';
9
+
10
+ const FORMAT_ICON_SIZE = 32;
11
+
12
+ function FormatIcon({ format }: { format: FileChipFormat }) {
13
+ if (format === 'csv') {
14
+ return <CsvIcon size={FORMAT_ICON_SIZE} />;
15
+ }
16
+
17
+ return (
18
+ <FileTextIcon
19
+ size={FORMAT_ICON_SIZE}
20
+ aria-hidden
21
+ style={{ color: 'var(--muted-foreground)' }}
22
+ />
23
+ );
24
+ }
25
+
26
+ function FileChipHeader({
27
+ name,
28
+ format,
29
+ hint,
30
+ onRemove,
31
+ }: Pick<FileChipProps, 'name' | 'format' | 'hint' | 'onRemove' | 'disabled'>) {
32
+ return (
33
+ <CardHeader
34
+ className={S.header}
35
+ icon={<FormatIcon format={format} />}
36
+ title={name}
37
+ description={hint}
38
+ titleClassName={S.title}
39
+ descriptionClassName={S.hint}
40
+ tooltipProps={{ maxWidth: 400 }}
41
+ >
42
+ {onRemove ? (
43
+ <CardAction>
44
+ <button
45
+ type="button"
46
+ className={S.remove}
47
+ aria-label={`Remove ${name}`}
48
+ onClick={onRemove}
49
+ >
50
+ <XIcon size={14} aria-hidden />
51
+ </button>
52
+ </CardAction>
53
+ ) : null}
54
+ </CardHeader>
55
+ );
56
+ }
57
+
58
+ export function FileChip({
59
+ name,
60
+ format,
61
+ hint,
62
+ onClick,
63
+ onRemove,
64
+ className,
65
+ }: FileChipProps) {
66
+ const header = (
67
+ <FileChipHeader
68
+ name={name}
69
+ format={format}
70
+ hint={hint}
71
+ onRemove={onRemove}
72
+ />
73
+ );
74
+
75
+ if (onClick) {
76
+ return (
77
+ <button
78
+ type="button"
79
+ className={cn(S.root, S.interactive, className)}
80
+ aria-label={`${hint}: ${name}`}
81
+ onClick={onClick}
82
+ >
83
+ {header}
84
+ </button>
85
+ );
86
+ }
87
+
88
+ return (
89
+ <div className={cn(S.root, className)} role="group">
90
+ {header}
91
+ </div>
92
+ );
93
+ }
@@ -0,0 +1,11 @@
1
+ export type FileChipFormat = 'csv' | 'pdf' | 'text';
2
+
3
+ export type FileChipProps = {
4
+ name: string;
5
+ format: FileChipFormat;
6
+ hint: string;
7
+ onClick?: () => void;
8
+ onRemove?: () => void;
9
+ disabled?: boolean;
10
+ className?: string;
11
+ };
@@ -0,0 +1,2 @@
1
+ export { FileChip } from './FileChip';
2
+ export type { FileChipProps, FileChipFormat } from './FileChip.types';
@@ -0,0 +1,162 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+
3
+ import {
4
+ type ChatAttachmentDropItem,
5
+ ChatChrome,
6
+ type Message,
7
+ MessageRole,
8
+ TEXT_ATTACHMENT_ACCEPT_PARTS,
9
+ } from '#uilib/components/ui/Chat';
10
+ import { PageContentSection } from '#uilib/components/ui/Page';
11
+ import { ScrollRef } from '@homecode/ui';
12
+
13
+ import { AppPageHeader } from '../components/AppPageHeader/AppPageHeader';
14
+ import { DocsHeaderActions } from '../docsHeaderActions';
15
+
16
+ const NO_QUICK_REPLY_KEYS: ReadonlySet<string> = new Set();
17
+
18
+ const ASSISTANT_REPLY =
19
+ 'Message received. Attachment text is included in the user bubble above.';
20
+
21
+ function makeMessage(role: MessageRole, text: string): Message {
22
+ return {
23
+ id: crypto.randomUUID(),
24
+ role,
25
+ text,
26
+ timestamp: Date.now(),
27
+ };
28
+ }
29
+
30
+ function formatAttachmentUserText(items: ChatAttachmentDropItem[]): string {
31
+ return items
32
+ .map(item => {
33
+ const preview =
34
+ item.text.length > 400
35
+ ? `${item.text.slice(0, 400).trim()}…`
36
+ : item.text.trim();
37
+ return `[${item.kind}] ${item.file.name}\n\n${preview || '(empty file)'}`;
38
+ })
39
+ .join('\n\n---\n\n');
40
+ }
41
+
42
+ function formatSubmittedMessage(
43
+ text: string,
44
+ attachments?: ChatAttachmentDropItem[],
45
+ ): string {
46
+ const parts = [
47
+ text.trim(),
48
+ attachments?.length ? formatAttachmentUserText(attachments) : '',
49
+ ].filter(Boolean);
50
+ return parts.join('\n\n');
51
+ }
52
+
53
+ export default function ChatAttachmentsDropzonePage() {
54
+ const [messages, setMessages] = useState<Message[]>([]);
55
+ const [isLoading, setIsLoading] = useState(false);
56
+ const replyTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
57
+ const scrollRef = useRef<ScrollRef>(null);
58
+
59
+ useEffect(() => {
60
+ return () => {
61
+ if (replyTimeoutRef.current != null) {
62
+ clearTimeout(replyTimeoutRef.current);
63
+ }
64
+ };
65
+ }, []);
66
+
67
+ const isEmpty = messages.length === 0 && !isLoading;
68
+ const isLastMessageFromUser =
69
+ messages.length > 0 &&
70
+ messages[messages.length - 1]?.role === MessageRole.USER;
71
+
72
+ const onPromptSubmit = useCallback(
73
+ (raw: string, attachments?: ChatAttachmentDropItem[]) => {
74
+ const body = formatSubmittedMessage(raw, attachments);
75
+ if (!body || isLoading) return;
76
+
77
+ setMessages(prev => [...prev, makeMessage(MessageRole.USER, body)]);
78
+ setIsLoading(true);
79
+
80
+ if (replyTimeoutRef.current != null) {
81
+ clearTimeout(replyTimeoutRef.current);
82
+ }
83
+ replyTimeoutRef.current = setTimeout(() => {
84
+ replyTimeoutRef.current = null;
85
+ setMessages(prev => [
86
+ ...prev,
87
+ makeMessage(MessageRole.ASSISTANT, ASSISTANT_REPLY),
88
+ ]);
89
+ setIsLoading(false);
90
+ }, 900);
91
+ },
92
+ [isLoading],
93
+ );
94
+
95
+ return (
96
+ <>
97
+ <AppPageHeader
98
+ breadcrumbs={[{ label: 'Chat' }, { label: 'Attachments dropzone' }]}
99
+ title="Chat — attachments dropzone"
100
+ subheader="Drop text files onto the chat shell; they appear on the prompt until you send."
101
+ actions={<DocsHeaderActions />}
102
+ />
103
+ <PageContentSection>
104
+ <p style={{ marginBottom: 16, fontSize: 14, lineHeight: 1.5 }}>
105
+ Drop a <code>.txt</code>, <code>.csv</code>, <code>.md</code>, or{' '}
106
+ <code>.pdf</code> file anywhere on the chat panel. The file shows
107
+ above the composer; press send to post it. Only MIME types from the
108
+ text allowlist are accepted; PDF parsing is optional via{' '}
109
+ <code>allowPdfAttachments</code>.
110
+ </p>
111
+ <ChatChrome
112
+ showResizeHandle={false}
113
+ resizeHandle={undefined}
114
+ onClose={undefined}
115
+ isEmpty={isEmpty}
116
+ renderPresets={() => null}
117
+ messages={messages}
118
+ onQuickReply={() => {}}
119
+ suppressedQuickReplyKeys={NO_QUICK_REPLY_KEYS}
120
+ isLoading={isLoading}
121
+ scriptContinueLabel={undefined}
122
+ onScriptContinue={undefined}
123
+ showBranchActionsRow={false}
124
+ showSyntheticBranchButtons={false}
125
+ unusedBranchKeys={[]}
126
+ isScriptComplete={false}
127
+ onGenerateDashboard={undefined}
128
+ generatingDashboard={false}
129
+ onGenerateDashboardClick={() => {}}
130
+ showInlinePresets={false}
131
+ isLastMessageFromUser={isLastMessageFromUser}
132
+ scrollRef={scrollRef}
133
+ effectiveScopeId="docs-chat-attachments-dropzone"
134
+ onPromptSubmit={onPromptSubmit}
135
+ onChatDeleted={() => {}}
136
+ allowedAttachments={[
137
+ 'text/plain',
138
+ '.txt',
139
+ 'text/csv',
140
+ '.csv',
141
+ 'text/markdown',
142
+ '.md',
143
+ 'application/json',
144
+ '.json',
145
+ ]}
146
+ allowPdfAttachments
147
+ emptyState={{
148
+ title: 'Drop a text file or PDF',
149
+ description:
150
+ 'Drag a file onto this panel, review it above the composer, then send.',
151
+ additionalContent: (
152
+ <p style={{ fontSize: 13, opacity: 0.85 }}>
153
+ Base allowlist includes{' '}
154
+ {TEXT_ATTACHMENT_ACCEPT_PARTS.slice(0, 6).join(', ')}…
155
+ </p>
156
+ ),
157
+ }}
158
+ />
159
+ </PageContentSection>
160
+ </>
161
+ );
162
+ }
@@ -0,0 +1,50 @@
1
+ import { FileChip } from '#uilib/components/ui/FileChip';
2
+ import { PageContentSection } from '#uilib/components/ui/Page';
3
+
4
+ import { AppPageHeader } from '../components/AppPageHeader/AppPageHeader';
5
+ import { DocsHeaderActions } from '../docsHeaderActions';
6
+
7
+ const LONG_NAME =
8
+ 'quarterly_procurement_open_orders_analysis_am_i_overpaying_export_final_v2.csv';
9
+
10
+ export default function FileChipPage() {
11
+ return (
12
+ <>
13
+ <AppPageHeader
14
+ breadcrumbs={[{ label: 'FileChip' }]}
15
+ title="FileChip"
16
+ subheader="Compact file row for chat attachments and downloadable CSV presets."
17
+ actions={<DocsHeaderActions />}
18
+ />
19
+ <PageContentSection
20
+ style={{
21
+ display: 'flex',
22
+ flexDirection: 'column',
23
+ alignItems: 'flex-start',
24
+ gap: '1rem',
25
+ }}
26
+ >
27
+ <FileChip name="report.pdf" format="pdf" hint="PDF" />
28
+ <FileChip
29
+ name="notes.txt"
30
+ format="text"
31
+ hint="Text file"
32
+ onRemove={() => {}}
33
+ />
34
+ <FileChip
35
+ name={LONG_NAME}
36
+ format="csv"
37
+ hint="Download CSV file"
38
+ onClick={() => {}}
39
+ />
40
+ <FileChip
41
+ name={LONG_NAME}
42
+ format="csv"
43
+ hint="Download CSV file"
44
+ onClick={() => {}}
45
+ onRemove={() => {}}
46
+ />
47
+ </PageContentSection>
48
+ </>
49
+ );
50
+ }
@@ -61,6 +61,12 @@ export const DOC_REGISTRY: DocEntry[] = [
61
61
  section: 'Data display',
62
62
  load: () => import('./pages/BadgePage'),
63
63
  },
64
+ {
65
+ slug: 'file-chip',
66
+ title: 'FileChip',
67
+ section: 'Data display',
68
+ load: () => import('./pages/FileChipPage'),
69
+ },
64
70
  {
65
71
  slug: 'breadcrumb',
66
72
  title: 'Breadcrumb',
@@ -109,6 +115,12 @@ export const DOC_REGISTRY: DocEntry[] = [
109
115
  section: 'Chat',
110
116
  load: () => import('./pages/ChatUserCsvAttachmentPage'),
111
117
  },
118
+ {
119
+ slug: 'chat-attachments-dropzone',
120
+ title: 'Chat attachments dropzone',
121
+ section: 'Chat',
122
+ load: () => import('./pages/ChatAttachmentsDropzonePage'),
123
+ },
112
124
  {
113
125
  slug: 'checkbox',
114
126
  title: 'Checkbox',
package/src/index.ts CHANGED
@@ -29,6 +29,7 @@ export * from './components/ui/Dialog';
29
29
  export * from './components/ui/Drawer';
30
30
  export * from './components/ui/DropdownMenu';
31
31
  export * from './components/ui/DropZone/DropZone';
32
+ export * from './components/ui/FileChip';
32
33
  export * from './components/ui/FlickeringGrid';
33
34
  export * from './components/ui/Foldable';
34
35
  export * from './components/ui/Gap/Gap';