@btst/stack 4.0.0 → 4.1.1

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 (116) hide show
  1. package/README.md +2 -2
  2. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-input.cjs +112 -103
  3. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-input.mjs +112 -103
  4. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.cjs +5 -2
  5. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.mjs +5 -2
  6. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-layout.cjs +99 -88
  7. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-layout.mjs +99 -88
  8. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-message.cjs +12 -3
  9. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-message.mjs +12 -3
  10. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-sidebar.cjs +6 -2
  11. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-sidebar.mjs +6 -2
  12. package/dist/packages/stack/src/plugins/blog/client/components/shared/page-wrapper.cjs +3 -2
  13. package/dist/packages/stack/src/plugins/blog/client/components/shared/page-wrapper.mjs +3 -2
  14. package/dist/packages/stack/src/plugins/blog/client/components/shared/post-card.cjs +81 -52
  15. package/dist/packages/stack/src/plugins/blog/client/components/shared/post-card.mjs +82 -53
  16. package/dist/packages/stack/src/plugins/blog/client/components/shared/posts-list.cjs +3 -2
  17. package/dist/packages/stack/src/plugins/blog/client/components/shared/posts-list.mjs +3 -2
  18. package/dist/packages/stack/src/plugins/cms/client/components/pages/content-list-page.internal.cjs +95 -75
  19. package/dist/packages/stack/src/plugins/cms/client/components/pages/content-list-page.internal.mjs +95 -75
  20. package/dist/packages/stack/src/plugins/cms/client/components/pages/dashboard-page.internal.cjs +16 -3
  21. package/dist/packages/stack/src/plugins/cms/client/components/pages/dashboard-page.internal.mjs +16 -3
  22. package/dist/packages/stack/src/plugins/cms/client/components/shared/page-wrapper.cjs +3 -2
  23. package/dist/packages/stack/src/plugins/cms/client/components/shared/page-wrapper.mjs +3 -2
  24. package/dist/packages/stack/src/plugins/cms/utils.cjs +6 -2
  25. package/dist/packages/stack/src/plugins/cms/utils.mjs +6 -3
  26. package/dist/packages/stack/src/plugins/form-builder/client/components/forms/form-renderer.cjs +14 -8
  27. package/dist/packages/stack/src/plugins/form-builder/client/components/forms/form-renderer.mjs +14 -8
  28. package/dist/packages/stack/src/plugins/form-builder/client/components/pages/form-list-page.internal.cjs +94 -87
  29. package/dist/packages/stack/src/plugins/form-builder/client/components/pages/form-list-page.internal.mjs +94 -87
  30. package/dist/packages/stack/src/plugins/form-builder/client/components/pages/submissions-page.internal.cjs +81 -74
  31. package/dist/packages/stack/src/plugins/form-builder/client/components/pages/submissions-page.internal.mjs +81 -74
  32. package/dist/packages/stack/src/plugins/form-builder/client/components/shared/page-wrapper.cjs +3 -2
  33. package/dist/packages/stack/src/plugins/form-builder/client/components/shared/page-wrapper.mjs +3 -2
  34. package/dist/packages/stack/src/plugins/form-builder/client/components/shared/pagination.cjs +25 -16
  35. package/dist/packages/stack/src/plugins/form-builder/client/components/shared/pagination.mjs +25 -16
  36. package/dist/packages/stack/src/plugins/form-builder/utils.cjs +6 -2
  37. package/dist/packages/stack/src/plugins/form-builder/utils.mjs +6 -3
  38. package/dist/plugins/ai-chat/client/components/index.d.cts +1 -1
  39. package/dist/plugins/ai-chat/client/components/index.d.mts +1 -1
  40. package/dist/plugins/ai-chat/client/components/index.d.ts +1 -1
  41. package/dist/plugins/ai-chat/client/index.d.cts +2 -2
  42. package/dist/plugins/ai-chat/client/index.d.mts +2 -2
  43. package/dist/plugins/ai-chat/client/index.d.ts +2 -2
  44. package/dist/plugins/blog/api/index.d.cts +2 -2
  45. package/dist/plugins/blog/api/index.d.mts +2 -2
  46. package/dist/plugins/blog/api/index.d.ts +2 -2
  47. package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
  48. package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
  49. package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
  50. package/dist/plugins/blog/client/index.d.cts +20 -2
  51. package/dist/plugins/blog/client/index.d.mts +20 -2
  52. package/dist/plugins/blog/client/index.d.ts +20 -2
  53. package/dist/plugins/blog/query-keys.d.cts +2 -2
  54. package/dist/plugins/blog/query-keys.d.mts +2 -2
  55. package/dist/plugins/blog/query-keys.d.ts +2 -2
  56. package/dist/plugins/cms/api/index.d.cts +1 -1
  57. package/dist/plugins/cms/api/index.d.mts +1 -1
  58. package/dist/plugins/cms/api/index.d.ts +1 -1
  59. package/dist/plugins/cms/client/index.d.cts +18 -0
  60. package/dist/plugins/cms/client/index.d.mts +18 -0
  61. package/dist/plugins/cms/client/index.d.ts +18 -0
  62. package/dist/plugins/cms/query-keys.d.cts +1 -1
  63. package/dist/plugins/cms/query-keys.d.mts +1 -1
  64. package/dist/plugins/cms/query-keys.d.ts +1 -1
  65. package/dist/plugins/form-builder/client/index.d.cts +16 -0
  66. package/dist/plugins/form-builder/client/index.d.mts +16 -0
  67. package/dist/plugins/form-builder/client/index.d.ts +16 -0
  68. package/dist/plugins/ui-builder/client/hooks/index.d.cts +1 -1
  69. package/dist/plugins/ui-builder/client/hooks/index.d.mts +1 -1
  70. package/dist/plugins/ui-builder/client/hooks/index.d.ts +1 -1
  71. package/dist/plugins/ui-builder/client/index.d.cts +1 -1
  72. package/dist/plugins/ui-builder/client/index.d.mts +1 -1
  73. package/dist/plugins/ui-builder/client/index.d.ts +1 -1
  74. package/dist/plugins/ui-builder/query-keys.d.cts +1 -1
  75. package/dist/plugins/ui-builder/query-keys.d.mts +1 -1
  76. package/dist/plugins/ui-builder/query-keys.d.ts +1 -1
  77. package/dist/shared/{stack.CjCSDfWy.d.mts → stack.94kN3pEK.d.mts} +5 -5
  78. package/dist/shared/{stack.BGzgFx3R.d.cts → stack.BTStHVTy.d.cts} +5 -5
  79. package/dist/shared/{stack.Dvy7FmaA.d.mts → stack.BggTHKlo.d.mts} +35 -35
  80. package/dist/shared/{stack.BOZRaVpu.d.cts → stack.CtrnoQgH.d.cts} +35 -35
  81. package/dist/shared/{stack.Dpg_JjIo.d.ts → stack.DfrRYOIl.d.ts} +5 -5
  82. package/dist/shared/{stack.DlBoNqYu.d.ts → stack.lPKwd_v0.d.ts} +35 -35
  83. package/dist/shared/{stack.5_zzCejP.d.cts → stack.rVF0f509.d.cts} +20 -0
  84. package/dist/shared/{stack.5_zzCejP.d.mts → stack.rVF0f509.d.mts} +20 -0
  85. package/dist/shared/{stack.5_zzCejP.d.ts → stack.rVF0f509.d.ts} +20 -0
  86. package/package.json +1 -1
  87. package/src/plugins/ai-chat/__tests__/classnames-overrides.test.tsx +248 -0
  88. package/src/plugins/ai-chat/client/components/chat-input.tsx +6 -1
  89. package/src/plugins/ai-chat/client/components/chat-interface.tsx +3 -0
  90. package/src/plugins/ai-chat/client/components/chat-layout.tsx +9 -2
  91. package/src/plugins/ai-chat/client/components/chat-message.tsx +13 -5
  92. package/src/plugins/ai-chat/client/components/chat-sidebar.tsx +6 -2
  93. package/src/plugins/ai-chat/client/overrides.ts +21 -0
  94. package/src/plugins/blog/__tests__/classnames-overrides.test.tsx +206 -0
  95. package/src/plugins/blog/client/components/shared/page-wrapper.tsx +3 -2
  96. package/src/plugins/blog/client/components/shared/post-card.tsx +32 -12
  97. package/src/plugins/blog/client/components/shared/posts-list.tsx +3 -2
  98. package/src/plugins/blog/client/overrides.ts +18 -0
  99. package/src/plugins/cms/__tests__/classnames-overrides.test.tsx +200 -0
  100. package/src/plugins/cms/client/components/pages/content-list-page.internal.tsx +17 -4
  101. package/src/plugins/cms/client/components/pages/dashboard-page.internal.tsx +14 -3
  102. package/src/plugins/cms/client/components/shared/page-wrapper.tsx +3 -2
  103. package/src/plugins/cms/client/overrides.ts +19 -0
  104. package/src/plugins/form-builder/__tests__/classnames-overrides.test.tsx +312 -0
  105. package/src/plugins/form-builder/client/components/forms/form-renderer.tsx +14 -9
  106. package/src/plugins/form-builder/client/components/pages/form-list-page.internal.tsx +9 -5
  107. package/src/plugins/form-builder/client/components/pages/submissions-page.internal.tsx +9 -5
  108. package/src/plugins/form-builder/client/components/shared/page-wrapper.tsx +3 -2
  109. package/src/plugins/form-builder/client/components/shared/pagination.tsx +7 -1
  110. package/src/plugins/form-builder/client/overrides.ts +17 -0
  111. package/dist/shared/{stack.CyalJ0j6.d.ts → stack.BMlTZnZp.d.ts} +16 -16
  112. package/dist/shared/{stack.D6Pg-ufX.d.cts → stack.BV0uNV4f.d.cts} +16 -16
  113. package/dist/shared/{stack.Csi-4Mgz.d.cts → stack.CKc_Bais.d.cts} +7 -7
  114. package/dist/shared/{stack.Csi-4Mgz.d.mts → stack.CKc_Bais.d.mts} +7 -7
  115. package/dist/shared/{stack.Csi-4Mgz.d.ts → stack.CKc_Bais.d.ts} +7 -7
  116. package/dist/shared/{stack.B8PG21zX.d.mts → stack.lsbdKRj7.d.mts} +16 -16
package/README.md CHANGED
@@ -52,13 +52,13 @@ Render the generated `<Toaster />` in your root layout and start from a clean co
52
52
  Run the released generator from the application root:
53
53
 
54
54
  ```bash
55
- pnpm dlx @btst/codegen@4.0.0 init \
55
+ pnpm dlx @btst/codegen@4.1.1 init \
56
56
  --framework nextjs \
57
57
  --adapter memory \
58
58
  --plugins blog
59
59
  ```
60
60
 
61
- The generator shows detected-file and conflict prompts before it writes. It installs `@btst/stack@4.0.0`, `next-themes@0.4.6`, registers Blog on the backend and client, mounts the API and page routes, adds the plugin CSS, and wires the shared provider.
61
+ The generator shows detected-file and conflict prompts before it writes. It installs `@btst/stack@4.1.1`, `next-themes@0.4.6`, registers Blog on the backend and client, mounts the API and page routes, adds the plugin CSS, and wires the shared provider.
62
62
 
63
63
  Set the trusted local origins in `.env.local`:
64
64
 
@@ -43,7 +43,8 @@ function ChatInput({
43
43
  const {
44
44
  uploadFile,
45
45
  localization: customLocalization,
46
- allowedFileTypes
46
+ allowedFileTypes,
47
+ classNames
47
48
  } = context.usePluginOverrides(
48
49
  "aiChat",
49
50
  {}
@@ -217,113 +218,121 @@ function ChatInput({
217
218
  )
218
219
  }
219
220
  ),
220
- /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleFormSubmit, className: "space-y-2", children: [
221
- attachedFiles.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2", children: attachedFiles.map((file, index) => /* @__PURE__ */ jsxRuntime.jsxs(
222
- "div",
223
- {
224
- className: "relative group rounded-lg overflow-hidden border",
225
- children: [
226
- isImageFile(file.mediaType) ? (
227
- // Image preview
221
+ /* @__PURE__ */ jsxRuntime.jsxs(
222
+ "form",
223
+ {
224
+ onSubmit: handleFormSubmit,
225
+ className: utils.cn("space-y-2", classNames?.input),
226
+ "data-testid": "chat-input-form",
227
+ children: [
228
+ attachedFiles.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2", children: attachedFiles.map((file, index) => /* @__PURE__ */ jsxRuntime.jsxs(
229
+ "div",
230
+ {
231
+ className: "relative group rounded-lg overflow-hidden border",
232
+ children: [
233
+ isImageFile(file.mediaType) ? (
234
+ // Image preview
235
+ /* @__PURE__ */ jsxRuntime.jsx(
236
+ "img",
237
+ {
238
+ src: file.url,
239
+ alt: file.filename,
240
+ className: "h-16 w-16 object-cover"
241
+ }
242
+ )
243
+ ) : (
244
+ // File preview with icon
245
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-32 flex items-center gap-2 p-2 bg-muted", children: [
246
+ /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.FileText, { className: "h-5 w-5 shrink-0 text-muted-foreground" }),
247
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs truncate", title: file.filename, children: file.filename })
248
+ ] })
249
+ ),
250
+ /* @__PURE__ */ jsxRuntime.jsx(
251
+ "button",
252
+ {
253
+ type: "button",
254
+ onClick: () => removeFile(index),
255
+ className: "absolute top-0.5 right-0.5 p-0.5 rounded-full bg-background/80 opacity-0 group-hover:opacity-100 transition-opacity",
256
+ "aria-label": tr(
257
+ "FILE_REMOVE",
258
+ "aiChat.files.remove",
259
+ "Remove file"
260
+ ),
261
+ children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.X, { className: "h-3 w-3" })
262
+ }
263
+ )
264
+ ]
265
+ },
266
+ index
267
+ )) }),
268
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center gap-2", children: [
269
+ canUploadFiles && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
228
270
  /* @__PURE__ */ jsxRuntime.jsx(
229
- "img",
271
+ "input",
230
272
  {
231
- src: file.url,
232
- alt: file.filename,
233
- className: "h-16 w-16 object-cover"
273
+ ref: fileInputRef,
274
+ type: "file",
275
+ accept: acceptString,
276
+ onChange: handleFileUpload,
277
+ className: "hidden"
234
278
  }
235
- )
236
- ) : (
237
- // File preview with icon
238
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-32 flex items-center gap-2 p-2 bg-muted", children: [
239
- /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.FileText, { className: "h-5 w-5 shrink-0 text-muted-foreground" }),
240
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs truncate", title: file.filename, children: file.filename })
241
- ] })
242
- ),
243
- /* @__PURE__ */ jsxRuntime.jsx(
244
- "button",
245
- {
246
- type: "button",
247
- onClick: () => removeFile(index),
248
- className: "absolute top-0.5 right-0.5 p-0.5 rounded-full bg-background/80 opacity-0 group-hover:opacity-100 transition-opacity",
249
- "aria-label": tr(
250
- "FILE_REMOVE",
251
- "aiChat.files.remove",
252
- "Remove file"
253
- ),
254
- children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.X, { className: "h-3 w-3" })
255
- }
256
- )
257
- ]
258
- },
259
- index
260
- )) }),
261
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center gap-2", children: [
262
- canUploadFiles && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
263
- /* @__PURE__ */ jsxRuntime.jsx(
264
- "input",
265
- {
266
- ref: fileInputRef,
267
- type: "file",
268
- accept: acceptString,
269
- onChange: handleFileUpload,
270
- className: "hidden"
271
- }
272
- ),
273
- /* @__PURE__ */ jsxRuntime.jsx(
274
- button.Button,
275
- {
276
- type: "button",
277
- variant: "ghost",
278
- size: "icon",
279
- onClick: () => fileInputRef.current?.click(),
280
- disabled: isLoading || isUploading,
281
- className: utils.cn("shrink-0", isCompact ? "h-8 w-8" : "h-9 w-9"),
282
- "aria-label": tr(
283
- "FILE_UPLOAD_BUTTON",
284
- "aiChat.files.attach",
285
- "Attach file"
286
279
  ),
287
- children: isUploading ? /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Paperclip, { className: "h-4 w-4" })
288
- }
289
- )
290
- ] }),
291
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex-1 min-w-0", children: [
292
- /* @__PURE__ */ jsxRuntime.jsx(
293
- textarea.Textarea,
294
- {
295
- value: input,
296
- onChange: handleInputChange,
297
- onKeyDown: handleKeyDown,
298
- placeholder: placeholder || tr(
299
- "CHAT_PLACEHOLDER",
300
- "aiChat.chat.placeholder",
301
- "Type a message..."
302
- ),
303
- className: utils.cn(
304
- "resize-none pr-12 max-w-full",
305
- isCompact ? "min-h-[40px] max-h-[120px] py-2" : "min-h-[50px] max-h-[200px] py-3"
306
- ),
307
- rows: 1
308
- }
309
- ),
310
- /* @__PURE__ */ jsxRuntime.jsx(
311
- button.Button,
312
- {
313
- type: "submit",
314
- size: "icon",
315
- disabled: isLoading || !input?.trim() && attachedFiles.length === 0,
316
- className: utils.cn(
317
- "absolute right-2 bottom-2",
318
- isCompact ? "h-7 w-7" : "h-8 w-8"
280
+ /* @__PURE__ */ jsxRuntime.jsx(
281
+ button.Button,
282
+ {
283
+ type: "button",
284
+ variant: "ghost",
285
+ size: "icon",
286
+ onClick: () => fileInputRef.current?.click(),
287
+ disabled: isLoading || isUploading,
288
+ className: utils.cn("shrink-0", isCompact ? "h-8 w-8" : "h-9 w-9"),
289
+ "aria-label": tr(
290
+ "FILE_UPLOAD_BUTTON",
291
+ "aiChat.files.attach",
292
+ "Attach file"
293
+ ),
294
+ children: isUploading ? /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Paperclip, { className: "h-4 w-4" })
295
+ }
296
+ )
297
+ ] }),
298
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex-1 min-w-0", children: [
299
+ /* @__PURE__ */ jsxRuntime.jsx(
300
+ textarea.Textarea,
301
+ {
302
+ value: input,
303
+ onChange: handleInputChange,
304
+ onKeyDown: handleKeyDown,
305
+ placeholder: placeholder || tr(
306
+ "CHAT_PLACEHOLDER",
307
+ "aiChat.chat.placeholder",
308
+ "Type a message..."
309
+ ),
310
+ className: utils.cn(
311
+ "resize-none pr-12 max-w-full",
312
+ isCompact ? "min-h-[40px] max-h-[120px] py-2" : "min-h-[50px] max-h-[200px] py-3"
313
+ ),
314
+ rows: 1
315
+ }
319
316
  ),
320
- "aria-label": tr("CHAT_SEND_BUTTON", "aiChat.chat.send", "Send"),
321
- children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Send, { className: utils.cn(isCompact ? "h-3.5 w-3.5" : "h-4 w-4") })
322
- }
323
- )
324
- ] })
325
- ] })
326
- ] })
317
+ /* @__PURE__ */ jsxRuntime.jsx(
318
+ button.Button,
319
+ {
320
+ type: "submit",
321
+ size: "icon",
322
+ disabled: isLoading || !input?.trim() && attachedFiles.length === 0,
323
+ className: utils.cn(
324
+ "absolute right-2 bottom-2",
325
+ isCompact ? "h-7 w-7" : "h-8 w-8"
326
+ ),
327
+ "aria-label": tr("CHAT_SEND_BUTTON", "aiChat.chat.send", "Send"),
328
+ children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Send, { className: utils.cn(isCompact ? "h-3.5 w-3.5" : "h-4 w-4") })
329
+ }
330
+ )
331
+ ] })
332
+ ] })
333
+ ]
334
+ }
335
+ )
327
336
  ] });
328
337
  }
329
338
 
@@ -41,7 +41,8 @@ function ChatInput({
41
41
  const {
42
42
  uploadFile,
43
43
  localization: customLocalization,
44
- allowedFileTypes
44
+ allowedFileTypes,
45
+ classNames
45
46
  } = usePluginOverrides(
46
47
  "aiChat",
47
48
  {}
@@ -215,113 +216,121 @@ function ChatInput({
215
216
  )
216
217
  }
217
218
  ),
218
- /* @__PURE__ */ jsxs("form", { onSubmit: handleFormSubmit, className: "space-y-2", children: [
219
- attachedFiles.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: attachedFiles.map((file, index) => /* @__PURE__ */ jsxs(
220
- "div",
221
- {
222
- className: "relative group rounded-lg overflow-hidden border",
223
- children: [
224
- isImageFile(file.mediaType) ? (
225
- // Image preview
219
+ /* @__PURE__ */ jsxs(
220
+ "form",
221
+ {
222
+ onSubmit: handleFormSubmit,
223
+ className: cn("space-y-2", classNames?.input),
224
+ "data-testid": "chat-input-form",
225
+ children: [
226
+ attachedFiles.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: attachedFiles.map((file, index) => /* @__PURE__ */ jsxs(
227
+ "div",
228
+ {
229
+ className: "relative group rounded-lg overflow-hidden border",
230
+ children: [
231
+ isImageFile(file.mediaType) ? (
232
+ // Image preview
233
+ /* @__PURE__ */ jsx(
234
+ "img",
235
+ {
236
+ src: file.url,
237
+ alt: file.filename,
238
+ className: "h-16 w-16 object-cover"
239
+ }
240
+ )
241
+ ) : (
242
+ // File preview with icon
243
+ /* @__PURE__ */ jsxs("div", { className: "w-32 flex items-center gap-2 p-2 bg-muted", children: [
244
+ /* @__PURE__ */ jsx(FileText, { className: "h-5 w-5 shrink-0 text-muted-foreground" }),
245
+ /* @__PURE__ */ jsx("span", { className: "text-xs truncate", title: file.filename, children: file.filename })
246
+ ] })
247
+ ),
248
+ /* @__PURE__ */ jsx(
249
+ "button",
250
+ {
251
+ type: "button",
252
+ onClick: () => removeFile(index),
253
+ className: "absolute top-0.5 right-0.5 p-0.5 rounded-full bg-background/80 opacity-0 group-hover:opacity-100 transition-opacity",
254
+ "aria-label": tr(
255
+ "FILE_REMOVE",
256
+ "aiChat.files.remove",
257
+ "Remove file"
258
+ ),
259
+ children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
260
+ }
261
+ )
262
+ ]
263
+ },
264
+ index
265
+ )) }),
266
+ /* @__PURE__ */ jsxs("div", { className: "relative flex items-center gap-2", children: [
267
+ canUploadFiles && /* @__PURE__ */ jsxs(Fragment, { children: [
226
268
  /* @__PURE__ */ jsx(
227
- "img",
269
+ "input",
228
270
  {
229
- src: file.url,
230
- alt: file.filename,
231
- className: "h-16 w-16 object-cover"
271
+ ref: fileInputRef,
272
+ type: "file",
273
+ accept: acceptString,
274
+ onChange: handleFileUpload,
275
+ className: "hidden"
232
276
  }
233
- )
234
- ) : (
235
- // File preview with icon
236
- /* @__PURE__ */ jsxs("div", { className: "w-32 flex items-center gap-2 p-2 bg-muted", children: [
237
- /* @__PURE__ */ jsx(FileText, { className: "h-5 w-5 shrink-0 text-muted-foreground" }),
238
- /* @__PURE__ */ jsx("span", { className: "text-xs truncate", title: file.filename, children: file.filename })
239
- ] })
240
- ),
241
- /* @__PURE__ */ jsx(
242
- "button",
243
- {
244
- type: "button",
245
- onClick: () => removeFile(index),
246
- className: "absolute top-0.5 right-0.5 p-0.5 rounded-full bg-background/80 opacity-0 group-hover:opacity-100 transition-opacity",
247
- "aria-label": tr(
248
- "FILE_REMOVE",
249
- "aiChat.files.remove",
250
- "Remove file"
251
- ),
252
- children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
253
- }
254
- )
255
- ]
256
- },
257
- index
258
- )) }),
259
- /* @__PURE__ */ jsxs("div", { className: "relative flex items-center gap-2", children: [
260
- canUploadFiles && /* @__PURE__ */ jsxs(Fragment, { children: [
261
- /* @__PURE__ */ jsx(
262
- "input",
263
- {
264
- ref: fileInputRef,
265
- type: "file",
266
- accept: acceptString,
267
- onChange: handleFileUpload,
268
- className: "hidden"
269
- }
270
- ),
271
- /* @__PURE__ */ jsx(
272
- Button,
273
- {
274
- type: "button",
275
- variant: "ghost",
276
- size: "icon",
277
- onClick: () => fileInputRef.current?.click(),
278
- disabled: isLoading || isUploading,
279
- className: cn("shrink-0", isCompact ? "h-8 w-8" : "h-9 w-9"),
280
- "aria-label": tr(
281
- "FILE_UPLOAD_BUTTON",
282
- "aiChat.files.attach",
283
- "Attach file"
284
277
  ),
285
- children: isUploading ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4" })
286
- }
287
- )
288
- ] }),
289
- /* @__PURE__ */ jsxs("div", { className: "relative flex-1 min-w-0", children: [
290
- /* @__PURE__ */ jsx(
291
- Textarea,
292
- {
293
- value: input,
294
- onChange: handleInputChange,
295
- onKeyDown: handleKeyDown,
296
- placeholder: placeholder || tr(
297
- "CHAT_PLACEHOLDER",
298
- "aiChat.chat.placeholder",
299
- "Type a message..."
300
- ),
301
- className: cn(
302
- "resize-none pr-12 max-w-full",
303
- isCompact ? "min-h-[40px] max-h-[120px] py-2" : "min-h-[50px] max-h-[200px] py-3"
304
- ),
305
- rows: 1
306
- }
307
- ),
308
- /* @__PURE__ */ jsx(
309
- Button,
310
- {
311
- type: "submit",
312
- size: "icon",
313
- disabled: isLoading || !input?.trim() && attachedFiles.length === 0,
314
- className: cn(
315
- "absolute right-2 bottom-2",
316
- isCompact ? "h-7 w-7" : "h-8 w-8"
278
+ /* @__PURE__ */ jsx(
279
+ Button,
280
+ {
281
+ type: "button",
282
+ variant: "ghost",
283
+ size: "icon",
284
+ onClick: () => fileInputRef.current?.click(),
285
+ disabled: isLoading || isUploading,
286
+ className: cn("shrink-0", isCompact ? "h-8 w-8" : "h-9 w-9"),
287
+ "aria-label": tr(
288
+ "FILE_UPLOAD_BUTTON",
289
+ "aiChat.files.attach",
290
+ "Attach file"
291
+ ),
292
+ children: isUploading ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4" })
293
+ }
294
+ )
295
+ ] }),
296
+ /* @__PURE__ */ jsxs("div", { className: "relative flex-1 min-w-0", children: [
297
+ /* @__PURE__ */ jsx(
298
+ Textarea,
299
+ {
300
+ value: input,
301
+ onChange: handleInputChange,
302
+ onKeyDown: handleKeyDown,
303
+ placeholder: placeholder || tr(
304
+ "CHAT_PLACEHOLDER",
305
+ "aiChat.chat.placeholder",
306
+ "Type a message..."
307
+ ),
308
+ className: cn(
309
+ "resize-none pr-12 max-w-full",
310
+ isCompact ? "min-h-[40px] max-h-[120px] py-2" : "min-h-[50px] max-h-[200px] py-3"
311
+ ),
312
+ rows: 1
313
+ }
317
314
  ),
318
- "aria-label": tr("CHAT_SEND_BUTTON", "aiChat.chat.send", "Send"),
319
- children: /* @__PURE__ */ jsx(Send, { className: cn(isCompact ? "h-3.5 w-3.5" : "h-4 w-4") })
320
- }
321
- )
322
- ] })
323
- ] })
324
- ] })
315
+ /* @__PURE__ */ jsx(
316
+ Button,
317
+ {
318
+ type: "submit",
319
+ size: "icon",
320
+ disabled: isLoading || !input?.trim() && attachedFiles.length === 0,
321
+ className: cn(
322
+ "absolute right-2 bottom-2",
323
+ isCompact ? "h-7 w-7" : "h-8 w-8"
324
+ ),
325
+ "aria-label": tr("CHAT_SEND_BUTTON", "aiChat.chat.send", "Send"),
326
+ children: /* @__PURE__ */ jsx(Send, { className: cn(isCompact ? "h-3.5 w-3.5" : "h-4 w-4") })
327
+ }
328
+ )
329
+ ] })
330
+ ] })
331
+ ]
332
+ }
333
+ )
325
334
  ] });
326
335
  }
327
336
 
@@ -163,7 +163,8 @@ function ChatInterface({
163
163
  const {
164
164
  localization: customLocalization,
165
165
  showAttribution,
166
- chatSuggestions
166
+ chatSuggestions,
167
+ classNames
167
168
  } = context.usePluginOverrides(
168
169
  "aiChat",
169
170
  { showAttribution: true }
@@ -797,8 +798,10 @@ function ChatInterface({
797
798
  {
798
799
  className: utils.cn(
799
800
  "flex flex-col p-4",
800
- isWidget ? "max-w-full" : "max-w-3xl mx-auto w-full"
801
+ isWidget ? "max-w-full" : "max-w-3xl mx-auto w-full",
802
+ classNames?.messageList
801
803
  ),
804
+ "data-testid": "chat-message-list",
802
805
  children: [
803
806
  messages.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col h-full min-h-[300px]", children: [
804
807
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex items-center justify-center text-muted-foreground mb-4", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: tr(
@@ -161,7 +161,8 @@ function ChatInterface({
161
161
  const {
162
162
  localization: customLocalization,
163
163
  showAttribution,
164
- chatSuggestions
164
+ chatSuggestions,
165
+ classNames
165
166
  } = usePluginOverrides(
166
167
  "aiChat",
167
168
  { showAttribution: true }
@@ -795,8 +796,10 @@ function ChatInterface({
795
796
  {
796
797
  className: cn(
797
798
  "flex flex-col p-4",
798
- isWidget ? "max-w-full" : "max-w-3xl mx-auto w-full"
799
+ isWidget ? "max-w-full" : "max-w-3xl mx-auto w-full",
800
+ classNames?.messageList
799
801
  ),
802
+ "data-testid": "chat-message-list",
800
803
  children: [
801
804
  messages.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full min-h-[300px]", children: [
802
805
  /* @__PURE__ */ jsx("div", { className: "flex-1 flex items-center justify-center text-muted-foreground mb-4", children: /* @__PURE__ */ jsx("p", { children: tr(