@slipstream-ui/ai 0.0.0-stage → 0.3.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 (111) hide show
  1. package/LICENSE +25 -0
  2. package/dist/index.d.mts +579 -0
  3. package/dist/index.d.ts +579 -0
  4. package/dist/index.js +1778 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/index.mjs +1706 -0
  7. package/dist/index.mjs.map +1 -0
  8. package/dist/manifests/attachment.manifest.json +33 -0
  9. package/dist/manifests/branch.manifest.json +65 -0
  10. package/dist/manifests/chain-thought.manifest.json +39 -0
  11. package/dist/manifests/code-block.manifest.json +50 -0
  12. package/dist/manifests/command-palette.manifest.json +80 -0
  13. package/dist/manifests/confirmation.manifest.json +143 -0
  14. package/dist/manifests/conversation.manifest.json +290 -0
  15. package/dist/manifests/inline-citation.manifest.json +46 -0
  16. package/dist/manifests/message-actions.manifest.json +35 -0
  17. package/dist/manifests/message.manifest.json +235 -0
  18. package/dist/manifests/prompt-input.manifest.json +334 -0
  19. package/dist/manifests/reasoning.manifest.json +177 -0
  20. package/dist/manifests/response.manifest.json +51 -0
  21. package/dist/manifests/source.manifest.json +78 -0
  22. package/dist/manifests/streaming-indicator.manifest.json +39 -0
  23. package/dist/manifests/suggestions.manifest.json +45 -0
  24. package/dist/manifests/tool-call.manifest.json +158 -0
  25. package/package.json +61 -3
  26. package/src/adapter/adapter.test.tsx +83 -0
  27. package/src/adapter/hooks.ts +82 -0
  28. package/src/adapter/index.ts +3 -0
  29. package/src/adapter/messages.ts +49 -0
  30. package/src/adapter/types.ts +24 -0
  31. package/src/components/attachment/Attachment.test.tsx +72 -0
  32. package/src/components/attachment/Attachment.tsx +84 -0
  33. package/src/components/attachment/AttachmentList.tsx +30 -0
  34. package/src/components/attachment/index.ts +5 -0
  35. package/src/components/branch/Branch.test.tsx +52 -0
  36. package/src/components/branch/Branch.tsx +77 -0
  37. package/src/components/branch/index.ts +2 -0
  38. package/src/components/chainthought/ChainThought.test.tsx +34 -0
  39. package/src/components/chainthought/ChainThought.tsx +45 -0
  40. package/src/components/chainthought/index.ts +2 -0
  41. package/src/components/code-block/CodeBlock.test.tsx +46 -0
  42. package/src/components/code-block/CodeBlock.tsx +95 -0
  43. package/src/components/code-block/index.ts +2 -0
  44. package/src/components/command-palette/CommandPalette.test.tsx +74 -0
  45. package/src/components/command-palette/CommandPalette.tsx +156 -0
  46. package/src/components/command-palette/index.ts +2 -0
  47. package/src/components/confirmation/Confirmation.test.tsx +97 -0
  48. package/src/components/confirmation/Confirmation.tsx +146 -0
  49. package/src/components/confirmation/index.ts +2 -0
  50. package/src/components/conversation/Conversation.test.tsx +49 -0
  51. package/src/components/conversation/Conversation.tsx +301 -0
  52. package/src/components/conversation/Conversation.welcome.test.tsx +73 -0
  53. package/src/components/conversation/index.ts +2 -0
  54. package/src/components/inline-citation/InlineCitation.test.tsx +42 -0
  55. package/src/components/inline-citation/InlineCitation.tsx +56 -0
  56. package/src/components/inline-citation/index.ts +2 -0
  57. package/src/components/message/Message.error-edit.test.tsx +60 -0
  58. package/src/components/message/Message.test.tsx +80 -0
  59. package/src/components/message/Message.tsx +205 -0
  60. package/src/components/message/index.ts +2 -0
  61. package/src/components/message-actions/MessageActions.test.tsx +77 -0
  62. package/src/components/message-actions/MessageActions.tsx +134 -0
  63. package/src/components/message-actions/index.ts +2 -0
  64. package/src/components/prompt-input/PromptInput.compound.test.tsx +150 -0
  65. package/src/components/prompt-input/PromptInput.test.tsx +74 -0
  66. package/src/components/prompt-input/PromptInput.tsx +584 -0
  67. package/src/components/prompt-input/index.ts +21 -0
  68. package/src/components/reasoning/Reasoning.duration.test.tsx +35 -0
  69. package/src/components/reasoning/Reasoning.test.tsx +58 -0
  70. package/src/components/reasoning/Reasoning.tsx +85 -0
  71. package/src/components/reasoning/index.ts +2 -0
  72. package/src/components/response/Response.test.tsx +69 -0
  73. package/src/components/response/Response.tsx +75 -0
  74. package/src/components/response/close-incomplete-markdown.ts +47 -0
  75. package/src/components/response/index.ts +3 -0
  76. package/src/components/source/Source.tsx +51 -0
  77. package/src/components/source/Sources.tsx +42 -0
  78. package/src/components/source/index.ts +5 -0
  79. package/src/components/source/source.test.tsx +72 -0
  80. package/src/components/streaming/StreamingIndicator.test.tsx +26 -0
  81. package/src/components/streaming/StreamingIndicator.tsx +46 -0
  82. package/src/components/streaming/index.ts +2 -0
  83. package/src/components/suggestion/Suggestion.test.tsx +43 -0
  84. package/src/components/suggestion/Suggestion.tsx +47 -0
  85. package/src/components/suggestion/index.ts +2 -0
  86. package/src/components/tool-call/ToolCall.test.tsx +45 -0
  87. package/src/components/tool-call/ToolCall.tsx +101 -0
  88. package/src/components/tool-call/index.ts +2 -0
  89. package/src/index.ts +29 -0
  90. package/src/manifests/attachment.manifest.json +33 -0
  91. package/src/manifests/branch.manifest.json +65 -0
  92. package/src/manifests/chain-thought.manifest.json +39 -0
  93. package/src/manifests/code-block.manifest.json +50 -0
  94. package/src/manifests/command-palette.manifest.json +80 -0
  95. package/src/manifests/confirmation.manifest.json +143 -0
  96. package/src/manifests/conversation.manifest.json +290 -0
  97. package/src/manifests/inline-citation.manifest.json +46 -0
  98. package/src/manifests/message-actions.manifest.json +35 -0
  99. package/src/manifests/message.manifest.json +235 -0
  100. package/src/manifests/prompt-input.manifest.json +334 -0
  101. package/src/manifests/reasoning.manifest.json +177 -0
  102. package/src/manifests/response.manifest.json +51 -0
  103. package/src/manifests/source.manifest.json +78 -0
  104. package/src/manifests/streaming-indicator.manifest.json +39 -0
  105. package/src/manifests/suggestions.manifest.json +45 -0
  106. package/src/manifests/tool-call.manifest.json +158 -0
  107. package/src/test/jest-axe.d.ts +16 -0
  108. package/src/test/setup.ts +18 -0
  109. package/src/test/vitest-matchers.d.ts +10 -0
  110. package/src/types/index.ts +40 -0
  111. package/README.md +0 -3
@@ -0,0 +1,235 @@
1
+ {
2
+ "id": "message",
3
+ "name": "Message",
4
+ "tier": "molecule",
5
+ "description": "A chat message component that displays messages from users, assistants, or system prompts with optional avatars",
6
+ "designIntent": "The Message component renders individual chat messages with proper styling based on the message role (user, assistant, system, tool). It handles avatar display, timestamps, and different visual treatments to distinguish between message types in a conversation interface.",
7
+ "props": [
8
+ {
9
+ "name": "message",
10
+ "type": "Message",
11
+ "required": true,
12
+ "description": "The message object containing role, content, and optional metadata",
13
+ "examples": [
14
+ "{ id: '1', role: 'user', content: 'Hello' }",
15
+ "{ id: '2', role: 'assistant', content: 'Hi there!' }"
16
+ ]
17
+ },
18
+ {
19
+ "name": "showAvatar",
20
+ "type": "boolean",
21
+ "defaultValue": "true",
22
+ "required": false,
23
+ "description": "Whether to show the avatar for the message",
24
+ "examples": [
25
+ true,
26
+ false
27
+ ]
28
+ },
29
+ {
30
+ "name": "avatarSrc",
31
+ "type": "string",
32
+ "required": false,
33
+ "description": "Optional custom avatar image URL",
34
+ "examples": [
35
+ "https://example.com/avatar.png"
36
+ ]
37
+ },
38
+ {
39
+ "name": "avatarFallback",
40
+ "type": "string",
41
+ "required": false,
42
+ "description": "Fallback text to show when avatar image is not available",
43
+ "examples": [
44
+ "JD",
45
+ "AI",
46
+ "Bot"
47
+ ]
48
+ },
49
+ {
50
+ "name": "className",
51
+ "type": "string",
52
+ "required": false,
53
+ "description": "Additional CSS classes to apply to the message container"
54
+ },
55
+ {
56
+ "name": "renderContent",
57
+ "type": "(content: string) => ReactNode",
58
+ "required": false,
59
+ "description": "Custom renderer (e.g. markdown); replaces default plain-text rendering."
60
+ },
61
+ {
62
+ "name": "isStreaming",
63
+ "type": "boolean",
64
+ "defaultValue": false,
65
+ "required": false,
66
+ "description": "Shows a typing cursor and sets aria-busy while the response is still generating."
67
+ },
68
+ {
69
+ "name": "userLabel",
70
+ "type": "string",
71
+ "defaultValue": "You",
72
+ "required": false,
73
+ "description": "Display name for user messages; enables localization."
74
+ },
75
+ {
76
+ "name": "assistantLabel",
77
+ "type": "string",
78
+ "defaultValue": "Assistant",
79
+ "required": false,
80
+ "description": "Display name for assistant messages; enables localization."
81
+ },
82
+ {
83
+ "name": "error",
84
+ "type": "string",
85
+ "required": false,
86
+ "description": "Error text to render on the message; shows an error card with retry"
87
+ },
88
+ {
89
+ "name": "onRetry",
90
+ "type": "() => void",
91
+ "required": false,
92
+ "description": "Called when the user requests a retry after error; omit to hide the action"
93
+ },
94
+ {
95
+ "name": "labels",
96
+ "type": "{ retry?: string; edit?: string; save?: string; cancel?: string }",
97
+ "required": false,
98
+ "description": "Override button labels in the message actions"
99
+ },
100
+ {
101
+ "name": "onEditContent",
102
+ "type": "(content: string) => void",
103
+ "required": false,
104
+ "description": "Enables edit-and-resubmit; called with the edited content"
105
+ }
106
+ ],
107
+ "examples": [
108
+ {
109
+ "title": "User message",
110
+ "code": "<Message message={{ id: '1', role: 'user', content: 'Hello, how are you?' }} />",
111
+ "description": "Message from the user with default avatar"
112
+ },
113
+ {
114
+ "title": "Assistant message",
115
+ "code": "<Message message={{ id: '2', role: 'assistant', content: 'I\\'m doing well, thank you!' }} />",
116
+ "description": "Message from the AI assistant"
117
+ },
118
+ {
119
+ "title": "System message",
120
+ "code": "<Message message={{ id: '3', role: 'system', content: 'System initialized' }} />",
121
+ "description": "System-level message with distinct styling"
122
+ },
123
+ {
124
+ "title": "With custom avatar",
125
+ "code": "<Message \n message={{ id: '4', role: 'assistant', content: 'Hello' }}\n avatarSrc=\"/ai-avatar.png\"\n avatarFallback=\"AI\"\n/>",
126
+ "description": "Message with custom avatar image and fallback"
127
+ },
128
+ {
129
+ "title": "Without avatar",
130
+ "code": "<Message \n message={{ id: '5', role: 'user', content: 'Quick message' }}\n showAvatar={false}\n/>",
131
+ "description": "Message with avatar hidden for more compact display"
132
+ },
133
+ {
134
+ "title": "Markdown rendering (opt-in)",
135
+ "code": "<Message message={m} renderContent={(c) => <ReactMarkdown>{c}</ReactMarkdown>} />"
136
+ },
137
+ {
138
+ "title": "Streaming response",
139
+ "code": "<Message message={partial} isStreaming />"
140
+ }
141
+ ],
142
+ "antiPatterns": [
143
+ {
144
+ "title": "Don't use for complex content",
145
+ "explanation": "Message content should be plain text. For complex UI, use additional components.",
146
+ "example": "// Avoid: Putting complex UI inside message content\n<Message message={{ \n role: 'assistant', \n content: '<div>Complex HTML</div>' \n}} />\n\n// Prefer: Use separate components for complex content\n<Message message={{ role: 'assistant', content: 'Here is the data:' }} />\n<DataDisplay data={...} />"
147
+ },
148
+ {
149
+ "title": "Don't hardcode message data",
150
+ "explanation": "Messages should come from a conversation state, not be hardcoded",
151
+ "example": "// Avoid: Hardcoded message\n<Message message={{ id: '1', role: 'user', content: 'Fixed text' }} />\n\n// Prefer: Dynamic message from state\n<Message message={messages[currentIndex]} />"
152
+ }
153
+ ],
154
+ "accessibility": [
155
+ {
156
+ "feature": "Screen reader support",
157
+ "implementation": "Message role and content are announced. Avatar has proper alt text from fallback or role",
158
+ "userRequirements": "Ensure avatarFallback is meaningful when avatarSrc is used"
159
+ },
160
+ {
161
+ "feature": "Color contrast",
162
+ "implementation": "User and assistant messages have distinct background colors with proper contrast",
163
+ "userRequirements": "Test color contrast in both light and dark modes"
164
+ },
165
+ {
166
+ "feature": "Semantic structure",
167
+ "implementation": "Message is rendered as a div with proper ARIA attributes for role identification",
168
+ "userRequirements": "Consider adding aria-live regions for streaming messages"
169
+ }
170
+ ],
171
+ "contentGuidelines": [
172
+ {
173
+ "contentType": "Message content",
174
+ "guidelines": [
175
+ "Content should be plain text, not HTML",
176
+ "Support markdown formatting if needed (apply at wrapper level)",
177
+ "Keep messages focused and concise",
178
+ "Use proper punctuation and grammar for assistant messages",
179
+ "For user messages, preserve user's original formatting"
180
+ ],
181
+ "goodExamples": [
182
+ "I can help you with that task.",
183
+ "Here's the code you requested:",
184
+ "Let me explain step by step."
185
+ ],
186
+ "badExamples": [
187
+ "<b>Bold text</b>",
188
+ "Lorem ipsum dolor sit amet",
189
+ "Click here to continue"
190
+ ]
191
+ },
192
+ {
193
+ "contentType": "Avatar fallback",
194
+ "guidelines": [
195
+ "Use 1-2 characters maximum",
196
+ "Use initials for user names",
197
+ "Use meaningful abbreviations for bots",
198
+ "Ensure high contrast with avatar background"
199
+ ],
200
+ "goodExamples": [
201
+ "JD",
202
+ "AI",
203
+ "Bot",
204
+ "S"
205
+ ],
206
+ "badExamples": [
207
+ "John Doe",
208
+ "Assistant",
209
+ "long-text"
210
+ ]
211
+ }
212
+ ],
213
+ "compositionRules": [
214
+ {
215
+ "component": "Conversation",
216
+ "relationship": "Messages are typically rendered within a Conversation component that manages the message list",
217
+ "example": "<Conversation>\n {messages.map(msg => <Message key={msg.id} message={msg} />)}\n</Conversation>"
218
+ },
219
+ {
220
+ "component": "PromptInput",
221
+ "relationship": "New messages from PromptInput should be added to the message list and rendered as Message components",
222
+ "example": "const handleSubmit = (text) => {\n const newMessage = { id: Date.now(), role: 'user', content: text };\n setMessages([...messages, newMessage]);\n}"
223
+ }
224
+ ],
225
+ "relatedComponents": [
226
+ "Conversation",
227
+ "PromptInput",
228
+ "Avatar"
229
+ ],
230
+ "package": "@slipstream-ui/ai",
231
+ "importPath": "@slipstream-ui/ai",
232
+ "version": "0.0.1",
233
+ "sourceDir": "message",
234
+ "exportName": "Message"
235
+ }
@@ -0,0 +1,334 @@
1
+ {
2
+ "id": "prompt-input",
3
+ "name": "PromptInput",
4
+ "tier": "molecule",
5
+ "description": "A textarea input for entering chat prompts with auto-resize, keyboard shortcuts, and send button",
6
+ "designIntent": "The PromptInput component provides a specialized input for chat interfaces. It handles auto-resizing as the user types, supports Enter to submit (Shift+Enter for new line), and integrates with the conversation flow. It's designed to be the primary way users interact with AI assistants.",
7
+ "props": [
8
+ {
9
+ "name": "onSubmit",
10
+ "type": "(value: string) => void",
11
+ "required": true,
12
+ "description": "Callback when the user submits the prompt",
13
+ "examples": [
14
+ "(value) => sendMessage(value)"
15
+ ]
16
+ },
17
+ {
18
+ "name": "placeholder",
19
+ "type": "string",
20
+ "defaultValue": "Type your message...",
21
+ "required": false,
22
+ "description": "Placeholder text to display when input is empty",
23
+ "examples": [
24
+ "Type your message...",
25
+ "Ask me anything...",
26
+ "Enter your prompt..."
27
+ ]
28
+ },
29
+ {
30
+ "name": "disabled",
31
+ "type": "boolean",
32
+ "defaultValue": "false",
33
+ "required": false,
34
+ "description": "Whether the input is disabled",
35
+ "examples": [
36
+ true,
37
+ false
38
+ ]
39
+ },
40
+ {
41
+ "name": "isLoading",
42
+ "type": "boolean",
43
+ "defaultValue": "false",
44
+ "required": false,
45
+ "description": "Whether a submission is currently in progress",
46
+ "examples": [
47
+ true,
48
+ false
49
+ ]
50
+ },
51
+ {
52
+ "name": "autoFocus",
53
+ "type": "boolean",
54
+ "defaultValue": "false",
55
+ "required": false,
56
+ "description": "Whether to auto-focus the input on mount",
57
+ "examples": [
58
+ true,
59
+ false
60
+ ]
61
+ },
62
+ {
63
+ "name": "maxLength",
64
+ "type": "number",
65
+ "required": false,
66
+ "description": "Maximum character count for the input",
67
+ "examples": [
68
+ 500,
69
+ 1000,
70
+ 2000
71
+ ]
72
+ },
73
+ {
74
+ "name": "showSendButton",
75
+ "type": "boolean",
76
+ "defaultValue": "true",
77
+ "required": false,
78
+ "description": "Whether to show the send button",
79
+ "examples": [
80
+ true,
81
+ false
82
+ ]
83
+ },
84
+ {
85
+ "name": "sendButtonText",
86
+ "type": "string",
87
+ "defaultValue": "Send",
88
+ "required": false,
89
+ "description": "Text or icon for the send button",
90
+ "examples": [
91
+ "Send",
92
+ "Submit",
93
+ "→"
94
+ ]
95
+ },
96
+ {
97
+ "name": "className",
98
+ "type": "string",
99
+ "required": false,
100
+ "description": "Additional CSS classes to apply to the container"
101
+ },
102
+ {
103
+ "name": "onStop",
104
+ "type": "() => void",
105
+ "required": false,
106
+ "description": "While loading, the send button becomes a stop button that calls this handler."
107
+ },
108
+ {
109
+ "name": "stopButtonText",
110
+ "type": "string",
111
+ "defaultValue": "Stop generating",
112
+ "required": false,
113
+ "description": "Accessible label for the stop button; enables localization."
114
+ },
115
+ {
116
+ "name": "attachments",
117
+ "type": "AttachmentItem[]",
118
+ "required": false,
119
+ "description": "Controlled attachment list shown as chips above the input"
120
+ },
121
+ {
122
+ "name": "onRemoveAttachment",
123
+ "type": "(id: string) => void",
124
+ "required": false,
125
+ "description": "Called when the user removes an attachment chip"
126
+ },
127
+ {
128
+ "name": "onAttachmentsChange",
129
+ "type": "(attachments: AttachmentItem[]) => void",
130
+ "required": false,
131
+ "description": "Called when files are added via drag-and-drop, paste, or picker"
132
+ },
133
+ {
134
+ "name": "hasError",
135
+ "type": "boolean",
136
+ "required": false,
137
+ "description": "Styles the input in an error state (e.g. after a rejected file)"
138
+ },
139
+ {
140
+ "name": "accept",
141
+ "type": "string",
142
+ "required": false,
143
+ "description": "MIME types accepted for attachments (e.g. \"image/*\")"
144
+ },
145
+ {
146
+ "name": "multiple",
147
+ "type": "boolean",
148
+ "required": false,
149
+ "description": "Allow selecting multiple files"
150
+ },
151
+ {
152
+ "name": "maxFiles",
153
+ "type": "number",
154
+ "required": false,
155
+ "description": "Maximum number of attachments allowed"
156
+ },
157
+ {
158
+ "name": "maxFileSize",
159
+ "type": "number",
160
+ "required": false,
161
+ "description": "Maximum file size in bytes; rejects larger files"
162
+ },
163
+ {
164
+ "name": "onAttachmentError",
165
+ "type": "(error: string) => void",
166
+ "required": false,
167
+ "description": "Called when an attachment is rejected (size, type, or count limit)"
168
+ },
169
+ {
170
+ "name": "globalDrop",
171
+ "type": "boolean",
172
+ "required": false,
173
+ "description": "Enable drop-anywhere-on-page file acceptance"
174
+ },
175
+ {
176
+ "name": "children",
177
+ "type": "ReactNode",
178
+ "required": false,
179
+ "description": "Custom content rendered inside the input row (e.g. tool buttons)"
180
+ },
181
+ {
182
+ "name": "tooltip",
183
+ "required": false,
184
+ "type": "string | { content: ReactNode; shortcut?: string }",
185
+ "description": "Tooltip content for a toolbar button; optionally includes a keyboard shortcut hint (PromptInput.Button sub-component)"
186
+ },
187
+ {
188
+ "name": "active",
189
+ "required": false,
190
+ "defaultValue": "false",
191
+ "type": "boolean",
192
+ "description": "Toggles the active state on a toolbar button (PromptInput.Button sub-component)"
193
+ }
194
+ ],
195
+ "examples": [
196
+ {
197
+ "title": "Basic prompt input",
198
+ "code": "<PromptInput onSubmit={(value) => console.log(value)} />",
199
+ "description": "Simple prompt input with default configuration"
200
+ },
201
+ {
202
+ "title": "With custom placeholder",
203
+ "code": "<PromptInput \n onSubmit={handleSubmit}\n placeholder=\"Ask me anything about coding...\"\n/>",
204
+ "description": "Prompt input with custom placeholder text"
205
+ },
206
+ {
207
+ "title": "With auto-focus",
208
+ "code": "<PromptInput \n onSubmit={handleSubmit}\n autoFocus={true}\n/>",
209
+ "description": "Auto-focuses the input when component mounts"
210
+ },
211
+ {
212
+ "title": "With character limit",
213
+ "code": "<PromptInput \n onSubmit={handleSubmit}\n maxLength={500}\n/>",
214
+ "description": "Limits input to 500 characters"
215
+ },
216
+ {
217
+ "title": "Without send button",
218
+ "code": "<PromptInput \n onSubmit={handleSubmit}\n showSendButton={false}\n/>",
219
+ "description": "Hides the send button (user must press Enter)"
220
+ },
221
+ {
222
+ "title": "With loading state",
223
+ "code": "<PromptInput \n onSubmit={handleSubmit}\n isLoading={isGenerating}\n disabled={isGenerating}\n/>",
224
+ "description": "Shows loading state and disables while AI is responding"
225
+ },
226
+ {
227
+ "title": "Cancellable generation",
228
+ "code": "<PromptInput onSubmit={send} isLoading={loading} onStop={() => abortRef.current.abort()} />"
229
+ }
230
+ ],
231
+ "antiPatterns": [
232
+ {
233
+ "title": "Don't use for non-chat input",
234
+ "explanation": "PromptInput is specifically for chat interactions, not general form input",
235
+ "example": "// Avoid: Using PromptInput for forms\n<PromptInput onSubmit={handleFormSubmit} />\n\n// Prefer: Use appropriate form input components\n<TextArea onChange={handleChange} />"
236
+ },
237
+ {
238
+ "title": "Don't ignore Enter key behavior",
239
+ "explanation": "Users expect Enter to submit. If you change this, communicate it clearly",
240
+ "example": "// Avoid: Confusing users by disabling Enter\n<PromptInput \n onSubmit={handleSubmit}\n // Users expect Enter to submit\n/>\n\n// If you need different behavior, document it clearly"
241
+ },
242
+ {
243
+ "title": "Don't validate after submission",
244
+ "explanation": "Disable the input when empty rather than validating in the handler",
245
+ "example": "// Avoid: Validating in onSubmit\n<PromptInput onSubmit={(value) => {\n if (!value) return; // Validate before\n sendMessage(value);\n}} />\n\n// Prefer: Disable button when empty\n<PromptInput \n onSubmit={sendMessage}\n disabled={!value}\n/>"
246
+ }
247
+ ],
248
+ "accessibility": [
249
+ {
250
+ "feature": "Keyboard navigation",
251
+ "implementation": "Full keyboard support with Enter to submit, Shift+Enter for new line",
252
+ "userRequirements": "Document keyboard shortcuts for users"
253
+ },
254
+ {
255
+ "feature": "Screen reader support",
256
+ "implementation": "Textarea has proper label and placeholder. Button state is announced",
257
+ "userRequirements": "Ensure placeholder text is descriptive and helpful"
258
+ },
259
+ {
260
+ "feature": "Form validation",
261
+ "implementation": "Button is disabled when input is empty, preventing invalid submissions",
262
+ "userRequirements": "Consider adding aria-invalid for custom validation if needed"
263
+ },
264
+ {
265
+ "feature": "Auto-resize",
266
+ "implementation": "Textarea automatically grows with content up to max-height",
267
+ "userRequirements": "Test with screen readers to ensure height changes are announced appropriately"
268
+ }
269
+ ],
270
+ "contentGuidelines": [
271
+ {
272
+ "contentType": "Placeholder text",
273
+ "guidelines": [
274
+ "Use action-oriented placeholder text",
275
+ "Be specific about what kind of input is expected",
276
+ "Keep placeholders concise (under 50 characters ideally)",
277
+ "Avoid generic placeholders like 'Enter text'",
278
+ "Consider the context of the conversation"
279
+ ],
280
+ "goodExamples": [
281
+ "Type your message...",
282
+ "Ask me anything about coding...",
283
+ "Describe what you'd like to build..."
284
+ ],
285
+ "badExamples": [
286
+ "Enter text here",
287
+ "Input",
288
+ "Type something",
289
+ "Lorem ipsum dolor sit amet"
290
+ ]
291
+ },
292
+ {
293
+ "contentType": "Send button",
294
+ "guidelines": [
295
+ "Use clear icons or text for the send button",
296
+ "Consider using an arrow icon (→) for a modern look",
297
+ "Show loading state when submitting",
298
+ "Disable button when input is empty"
299
+ ],
300
+ "goodExamples": [
301
+ "→",
302
+ "Send",
303
+ "Submit"
304
+ ],
305
+ "badExamples": [
306
+ "Click me",
307
+ "OK",
308
+ "Go"
309
+ ]
310
+ }
311
+ ],
312
+ "compositionRules": [
313
+ {
314
+ "component": "Conversation",
315
+ "relationship": "PromptInput is typically used below a Conversation component to add new messages",
316
+ "example": "Conversation displays messages, PromptInput submits new ones to the same messages array"
317
+ },
318
+ {
319
+ "component": "ChatLayout",
320
+ "relationship": "PromptInput is often part of a ChatLayout that combines Conversation and input",
321
+ "example": "ChatLayout wraps Conversation and PromptInput in a complete chat interface"
322
+ }
323
+ ],
324
+ "relatedComponents": [
325
+ "Conversation",
326
+ "ChatLayout",
327
+ "Button"
328
+ ],
329
+ "package": "@slipstream-ui/ai",
330
+ "importPath": "@slipstream-ui/ai",
331
+ "version": "0.0.1",
332
+ "sourceDir": "prompt-input",
333
+ "exportName": "PromptInput"
334
+ }