@agent-native/toolkit 0.9.1 → 0.10.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 (194) hide show
  1. package/agent-native.eject.json +71 -0
  2. package/dist/chat-history/ChatHistoryRail.d.ts +15 -1
  3. package/dist/chat-history/ChatHistoryRail.d.ts.map +1 -1
  4. package/dist/chat-history/ChatHistoryRail.js +26 -13
  5. package/dist/chat-history/ChatHistoryRail.js.map +1 -1
  6. package/dist/chat-history/ChatHistoryRail.spec.js +77 -1
  7. package/dist/chat-history/ChatHistoryRail.spec.js.map +1 -1
  8. package/dist/chat-history/index.d.ts +2 -1
  9. package/dist/chat-history/index.d.ts.map +1 -1
  10. package/dist/chat-history/index.js +2 -1
  11. package/dist/chat-history/index.js.map +1 -1
  12. package/dist/chat-history/useChatHistoryRailController.d.ts +26 -0
  13. package/dist/chat-history/useChatHistoryRailController.d.ts.map +1 -0
  14. package/dist/chat-history/useChatHistoryRailController.js +36 -0
  15. package/dist/chat-history/useChatHistoryRailController.js.map +1 -0
  16. package/dist/chat-history/useChatHistoryRailController.spec.d.ts +2 -0
  17. package/dist/chat-history/useChatHistoryRailController.spec.d.ts.map +1 -0
  18. package/dist/chat-history/useChatHistoryRailController.spec.js +101 -0
  19. package/dist/chat-history/useChatHistoryRailController.spec.js.map +1 -0
  20. package/dist/chat-history.css +2 -0
  21. package/dist/composer/ComposerPlusMenu.js +1 -1
  22. package/dist/composer/ComposerPlusMenu.js.map +1 -1
  23. package/dist/composer/PromptComposer.d.ts +1 -0
  24. package/dist/composer/PromptComposer.d.ts.map +1 -1
  25. package/dist/composer/PromptComposer.js +26 -5
  26. package/dist/composer/PromptComposer.js.map +1 -1
  27. package/dist/composer/PromptComposer.spec.js +8 -1
  28. package/dist/composer/PromptComposer.spec.js.map +1 -1
  29. package/dist/composer/RealtimeVoiceMode.d.ts.map +1 -1
  30. package/dist/composer/RealtimeVoiceMode.js +56 -11
  31. package/dist/composer/RealtimeVoiceMode.js.map +1 -1
  32. package/dist/composer/RealtimeVoiceMode.spec.js +59 -7
  33. package/dist/composer/RealtimeVoiceMode.spec.js.map +1 -1
  34. package/dist/composer/useRealtimeVoiceMode.d.ts +7 -0
  35. package/dist/composer/useRealtimeVoiceMode.d.ts.map +1 -1
  36. package/dist/composer/useRealtimeVoiceMode.js +110 -6
  37. package/dist/composer/useRealtimeVoiceMode.js.map +1 -1
  38. package/dist/composer/useRealtimeVoiceMode.spec.js +74 -2
  39. package/dist/composer/useRealtimeVoiceMode.spec.js.map +1 -1
  40. package/dist/conformance/__fixtures__/css-in-js-adapter.d.ts +7 -0
  41. package/dist/conformance/__fixtures__/css-in-js-adapter.d.ts.map +1 -0
  42. package/dist/conformance/__fixtures__/css-in-js-adapter.js +84 -0
  43. package/dist/conformance/__fixtures__/css-in-js-adapter.js.map +1 -0
  44. package/dist/conformance/conformance.spec.d.ts +2 -0
  45. package/dist/conformance/conformance.spec.d.ts.map +1 -0
  46. package/dist/conformance/conformance.spec.js +87 -0
  47. package/dist/conformance/conformance.spec.js.map +1 -0
  48. package/dist/conformance/index.d.ts +4 -0
  49. package/dist/conformance/index.d.ts.map +1 -0
  50. package/dist/conformance/index.js +4 -0
  51. package/dist/conformance/index.js.map +1 -0
  52. package/dist/conformance/runner.d.ts +12 -0
  53. package/dist/conformance/runner.d.ts.map +1 -0
  54. package/dist/conformance/runner.js +515 -0
  55. package/dist/conformance/runner.js.map +1 -0
  56. package/dist/conformance/types.d.ts +26 -0
  57. package/dist/conformance/types.d.ts.map +1 -0
  58. package/dist/conformance/types.js +13 -0
  59. package/dist/conformance/types.js.map +1 -0
  60. package/dist/conformance/version.d.ts +6 -0
  61. package/dist/conformance/version.d.ts.map +1 -0
  62. package/dist/conformance/version.js +14 -0
  63. package/dist/conformance/version.js.map +1 -0
  64. package/dist/design-system/components.d.ts +64 -0
  65. package/dist/design-system/components.d.ts.map +1 -0
  66. package/dist/design-system/components.js +50 -0
  67. package/dist/design-system/components.js.map +1 -0
  68. package/dist/design-system/context.d.ts +11 -0
  69. package/dist/design-system/context.d.ts.map +1 -0
  70. package/dist/design-system/context.js +17 -0
  71. package/dist/design-system/context.js.map +1 -0
  72. package/dist/design-system/default-adapter.d.ts +3 -0
  73. package/dist/design-system/default-adapter.d.ts.map +1 -0
  74. package/dist/design-system/default-adapter.js +166 -0
  75. package/dist/design-system/default-adapter.js.map +1 -0
  76. package/dist/design-system/default-adapter.spec.d.ts +2 -0
  77. package/dist/design-system/default-adapter.spec.d.ts.map +1 -0
  78. package/dist/design-system/default-adapter.spec.js +96 -0
  79. package/dist/design-system/default-adapter.spec.js.map +1 -0
  80. package/dist/design-system/definition.d.ts +9 -0
  81. package/dist/design-system/definition.d.ts.map +1 -0
  82. package/dist/design-system/definition.js +4 -0
  83. package/dist/design-system/definition.js.map +1 -0
  84. package/dist/design-system/design-system.spec.d.ts +2 -0
  85. package/dist/design-system/design-system.spec.d.ts.map +1 -0
  86. package/dist/design-system/design-system.spec.js +121 -0
  87. package/dist/design-system/design-system.spec.js.map +1 -0
  88. package/dist/design-system/error-boundary.d.ts +18 -0
  89. package/dist/design-system/error-boundary.d.ts.map +1 -0
  90. package/dist/design-system/error-boundary.js +16 -0
  91. package/dist/design-system/error-boundary.js.map +1 -0
  92. package/dist/design-system/index.d.ts +8 -0
  93. package/dist/design-system/index.d.ts.map +1 -0
  94. package/dist/design-system/index.js +8 -0
  95. package/dist/design-system/index.js.map +1 -0
  96. package/dist/design-system/theme.d.ts +30 -0
  97. package/dist/design-system/theme.d.ts.map +1 -0
  98. package/dist/design-system/theme.js +95 -0
  99. package/dist/design-system/theme.js.map +1 -0
  100. package/dist/design-system/theme.spec.d.ts +2 -0
  101. package/dist/design-system/theme.spec.d.ts.map +1 -0
  102. package/dist/design-system/theme.spec.js +43 -0
  103. package/dist/design-system/theme.spec.js.map +1 -0
  104. package/dist/design-system/types.d.ts +288 -0
  105. package/dist/design-system/types.d.ts.map +1 -0
  106. package/dist/design-system/types.js +2 -0
  107. package/dist/design-system/types.js.map +1 -0
  108. package/dist/editor/SharedRichEditor.d.ts.map +1 -1
  109. package/dist/editor/SharedRichEditor.js +3 -2
  110. package/dist/editor/SharedRichEditor.js.map +1 -1
  111. package/dist/editor/SlashCommandMenu.d.ts +8 -0
  112. package/dist/editor/SlashCommandMenu.d.ts.map +1 -1
  113. package/dist/editor/SlashCommandMenu.js +6 -0
  114. package/dist/editor/SlashCommandMenu.js.map +1 -1
  115. package/dist/editor/SlashCommandMenu.spec.d.ts +2 -0
  116. package/dist/editor/SlashCommandMenu.spec.d.ts.map +1 -0
  117. package/dist/editor/SlashCommandMenu.spec.js +20 -0
  118. package/dist/editor/SlashCommandMenu.spec.js.map +1 -0
  119. package/dist/editor/index.d.ts +1 -1
  120. package/dist/editor/index.d.ts.map +1 -1
  121. package/dist/editor/index.js +1 -1
  122. package/dist/editor/index.js.map +1 -1
  123. package/dist/provider.d.ts +4 -3
  124. package/dist/provider.d.ts.map +1 -1
  125. package/dist/provider.js +46 -4
  126. package/dist/provider.js.map +1 -1
  127. package/dist/styles.css +52 -23
  128. package/dist/ui/button.d.ts +5 -0
  129. package/dist/ui/button.d.ts.map +1 -1
  130. package/dist/ui/button.js +25 -4
  131. package/dist/ui/button.js.map +1 -1
  132. package/dist/ui/dialog.d.ts +4 -0
  133. package/dist/ui/dialog.d.ts.map +1 -1
  134. package/dist/ui/dialog.js +2 -2
  135. package/dist/ui/dialog.js.map +1 -1
  136. package/dist/ui/popover.d.ts +1 -0
  137. package/dist/ui/popover.d.ts.map +1 -1
  138. package/dist/ui/popover.js +2 -2
  139. package/dist/ui/popover.js.map +1 -1
  140. package/dist/ui/select.d.ts +3 -1
  141. package/dist/ui/select.d.ts.map +1 -1
  142. package/dist/ui/select.js +1 -1
  143. package/dist/ui/select.js.map +1 -1
  144. package/dist/ui/sidebar.d.ts +6 -2
  145. package/dist/ui/sidebar.d.ts.map +1 -1
  146. package/dist/ui/sidebar.js +12 -15
  147. package/dist/ui/sidebar.js.map +1 -1
  148. package/dist/ui/tooltip.d.ts +3 -1
  149. package/dist/ui/tooltip.d.ts.map +1 -1
  150. package/dist/ui/tooltip.js +2 -2
  151. package/dist/ui/tooltip.js.map +1 -1
  152. package/package.json +31 -1
  153. package/src/chat-history/ChatHistoryRail.spec.tsx +174 -0
  154. package/src/chat-history/ChatHistoryRail.tsx +100 -39
  155. package/src/chat-history/index.ts +9 -0
  156. package/src/chat-history/useChatHistoryRailController.spec.tsx +131 -0
  157. package/src/chat-history/useChatHistoryRailController.ts +76 -0
  158. package/src/chat-history.css +2 -0
  159. package/src/composer/ComposerPlusMenu.tsx +11 -8
  160. package/src/composer/PromptComposer.spec.ts +12 -1
  161. package/src/composer/PromptComposer.tsx +32 -7
  162. package/src/composer/RealtimeVoiceMode.spec.tsx +117 -9
  163. package/src/composer/RealtimeVoiceMode.tsx +119 -31
  164. package/src/composer/useRealtimeVoiceMode.spec.ts +101 -1
  165. package/src/composer/useRealtimeVoiceMode.tsx +124 -9
  166. package/src/conformance/__fixtures__/css-in-js-adapter.tsx +401 -0
  167. package/src/conformance/conformance.spec.tsx +143 -0
  168. package/src/conformance/index.ts +3 -0
  169. package/src/conformance/runner.tsx +862 -0
  170. package/src/conformance/types.ts +45 -0
  171. package/src/conformance/version.ts +22 -0
  172. package/src/design-system/components.tsx +146 -0
  173. package/src/design-system/context.tsx +31 -0
  174. package/src/design-system/default-adapter.spec.tsx +143 -0
  175. package/src/design-system/default-adapter.tsx +915 -0
  176. package/src/design-system/definition.ts +14 -0
  177. package/src/design-system/design-system.spec.tsx +183 -0
  178. package/src/design-system/error-boundary.tsx +37 -0
  179. package/src/design-system/index.ts +7 -0
  180. package/src/design-system/theme.spec.ts +61 -0
  181. package/src/design-system/theme.ts +145 -0
  182. package/src/design-system/types.ts +363 -0
  183. package/src/editor/SharedRichEditor.tsx +13 -2
  184. package/src/editor/SlashCommandMenu.spec.ts +33 -0
  185. package/src/editor/SlashCommandMenu.tsx +20 -0
  186. package/src/editor/index.ts +2 -0
  187. package/src/provider.tsx +98 -5
  188. package/src/styles.css +52 -23
  189. package/src/ui/button.tsx +95 -6
  190. package/src/ui/dialog.tsx +48 -23
  191. package/src/ui/popover.tsx +16 -2
  192. package/src/ui/select.tsx +7 -3
  193. package/src/ui/sidebar.tsx +47 -48
  194. package/src/ui/tooltip.tsx +7 -3
@@ -9,9 +9,9 @@ function normalizeTooltipText(text) {
9
9
  const decoded = text.replace(/\\u([0-9a-fA-F]{4})/g, (_match, hex) => String.fromCharCode(Number.parseInt(hex, 16)));
10
10
  return decoded.replace(/\b([A-Za-z][A-Za-z ]*?)\((?=(?:⌘|⌃|⌥|⇧|Ctrl|Alt|Shift|Cmd))/g, (_match, label) => `${label.charAt(0).toUpperCase()}${label.slice(1)} (`);
11
11
  }
12
- const TooltipContent = React.forwardRef(({ className, sideOffset = 6, children, ...props }, ref) => {
12
+ const TooltipContent = React.forwardRef(({ className, sideOffset = 6, children, container, ...props }, ref) => {
13
13
  const normalizedChildren = typeof children === "string" ? normalizeTooltipText(children) : children;
14
- return (_jsx(TooltipPrimitive.Portal, { children: _jsx(TooltipPrimitive.Content, { ref: ref, sideOffset: sideOffset, "data-agent-native-tooltip": "true", className: cn("z-[300] overflow-hidden rounded-md border border-border bg-popover px-2 py-1 text-[11px] text-foreground shadow-md origin-[var(--radix-tooltip-content-transform-origin)] data-[state=delayed-open]:animate-in data-[state=closed]:animate-out data-[state=delayed-open]:fade-in-0 data-[state=closed]:fade-out-0 data-[state=delayed-open]:zoom-in-95 data-[state=closed]:zoom-out-95 data-[state=delayed-open]:duration-150 data-[state=closed]:duration-100 motion-reduce:data-[state=delayed-open]:zoom-in-100 motion-reduce:data-[state=closed]:zoom-out-100", className), ...props, children: normalizedChildren }) }));
14
+ return (_jsx(TooltipPrimitive.Portal, { container: container, children: _jsx(TooltipPrimitive.Content, { ref: ref, sideOffset: sideOffset, "data-agent-native-tooltip": "true", className: cn("z-[300] overflow-hidden rounded-md border border-border bg-popover px-2 py-1 text-[11px] text-foreground shadow-md origin-[var(--radix-tooltip-content-transform-origin)] data-[state=delayed-open]:animate-in data-[state=closed]:animate-out data-[state=delayed-open]:fade-in-0 data-[state=closed]:fade-out-0 data-[state=delayed-open]:zoom-in-95 data-[state=closed]:zoom-out-95 data-[state=delayed-open]:duration-150 data-[state=closed]:duration-100 motion-reduce:data-[state=delayed-open]:zoom-in-100 motion-reduce:data-[state=closed]:zoom-out-100", className), ...props, children: normalizedChildren }) }));
15
15
  });
16
16
  TooltipContent.displayName = TooltipPrimitive.Content.displayName;
17
17
  export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider, normalizeTooltipText, };
@@ -1 +1 @@
1
- {"version":3,"file":"tooltip.js","sourceRoot":"","sources":["../../src/ui/tooltip.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,gBAAgB,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAEjC,MAAM,eAAe,GAAG,gBAAgB,CAAC,QAAQ,CAAC;AAElD,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC;AAEtC,MAAM,cAAc,GAAG,gBAAgB,CAAC,OAAO,CAAC;AAEhD,SAAS,oBAAoB,CAAC,IAAY;IACxC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,sBAAsB,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,CACnE,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAC9C,CAAC;IACF,OAAO,OAAO,CAAC,OAAO,CACpB,8DAA8D,EAC9D,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CACzE,CAAC;AACJ,CAAC;AAED,MAAM,cAAc,GAAG,KAAK,CAAC,UAAU,CAGrC,CAAC,EAAE,SAAS,EAAE,UAAU,GAAG,CAAC,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE;IAC3D,MAAM,kBAAkB,GACtB,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC3E,OAAO,CACL,KAAC,gBAAgB,CAAC,MAAM,cACtB,KAAC,gBAAgB,CAAC,OAAO,IACvB,GAAG,EAAE,GAAG,EACR,UAAU,EAAE,UAAU,+BACI,MAAM,EAChC,SAAS,EAAE,EAAE,CACX,miBAAmiB,EACniB,SAAS,CACV,KACG,KAAK,YAER,kBAAkB,GACM,GACH,CAC3B,CAAC;AACJ,CAAC,CAAC,CAAC;AACH,cAAc,CAAC,WAAW,GAAG,gBAAgB,CAAC,OAAO,CAAC,WAAW,CAAC;AAElE,OAAO,EACL,OAAO,EACP,cAAc,EACd,cAAc,EACd,eAAe,EACf,oBAAoB,GACrB,CAAC"}
1
+ {"version":3,"file":"tooltip.js","sourceRoot":"","sources":["../../src/ui/tooltip.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,gBAAgB,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAEjC,MAAM,eAAe,GAAG,gBAAgB,CAAC,QAAQ,CAAC;AAElD,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC;AAEtC,MAAM,cAAc,GAAG,gBAAgB,CAAC,OAAO,CAAC;AAEhD,SAAS,oBAAoB,CAAC,IAAY;IACxC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,sBAAsB,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,CACnE,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAC9C,CAAC;IACF,OAAO,OAAO,CAAC,OAAO,CACpB,8DAA8D,EAC9D,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CACzE,CAAC;AACJ,CAAC;AAED,MAAM,cAAc,GAAG,KAAK,CAAC,UAAU,CAOrC,CAAC,EAAE,SAAS,EAAE,UAAU,GAAG,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE;IACtE,MAAM,kBAAkB,GACtB,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC3E,OAAO,CACL,KAAC,gBAAgB,CAAC,MAAM,IAAC,SAAS,EAAE,SAAS,YAC3C,KAAC,gBAAgB,CAAC,OAAO,IACvB,GAAG,EAAE,GAAG,EACR,UAAU,EAAE,UAAU,+BACI,MAAM,EAChC,SAAS,EAAE,EAAE,CACX,miBAAmiB,EACniB,SAAS,CACV,KACG,KAAK,YAER,kBAAkB,GACM,GACH,CAC3B,CAAC;AACJ,CAAC,CAAC,CAAC;AACH,cAAc,CAAC,WAAW,GAAG,gBAAgB,CAAC,OAAO,CAAC,WAAW,CAAC;AAElE,OAAO,EACL,OAAO,EACP,cAAc,EACd,cAAc,EACd,eAAe,EACf,oBAAoB,GACrB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/toolkit",
3
- "version": "0.9.1",
3
+ "version": "0.10.1",
4
4
  "description": "Reusable app-building UI and helpers for Agent-Native apps.",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -103,6 +103,20 @@
103
103
  "import": "./dist/composer/*.js",
104
104
  "default": "./dist/composer/*.js"
105
105
  },
106
+ "./conformance": {
107
+ "types": "./dist/conformance/index.d.ts",
108
+ "browser": "./dist/conformance/index.js",
109
+ "development": "./dist/conformance/index.js",
110
+ "import": "./dist/conformance/index.js",
111
+ "default": "./dist/conformance/index.js"
112
+ },
113
+ "./conformance/*": {
114
+ "types": "./dist/conformance/*.d.ts",
115
+ "browser": "./dist/conformance/*.js",
116
+ "development": "./dist/conformance/*.js",
117
+ "import": "./dist/conformance/*.js",
118
+ "default": "./dist/conformance/*.js"
119
+ },
106
120
  "./context-ui": {
107
121
  "types": "./dist/context-ui/index.d.ts",
108
122
  "browser": "./dist/context-ui/index.js",
@@ -117,6 +131,20 @@
117
131
  "import": "./dist/design-tweaks/index.js",
118
132
  "default": "./dist/design-tweaks/index.js"
119
133
  },
134
+ "./design-system": {
135
+ "types": "./dist/design-system/index.d.ts",
136
+ "browser": "./dist/design-system/index.js",
137
+ "development": "./dist/design-system/index.js",
138
+ "import": "./dist/design-system/index.js",
139
+ "default": "./dist/design-system/index.js"
140
+ },
141
+ "./design-system/*": {
142
+ "types": "./dist/design-system/*.d.ts",
143
+ "browser": "./dist/design-system/*.js",
144
+ "development": "./dist/design-system/*.js",
145
+ "import": "./dist/design-system/*.js",
146
+ "default": "./dist/design-system/*.js"
147
+ },
120
148
  "./editor": {
121
149
  "types": "./dist/editor/index.d.ts",
122
150
  "browser": "./dist/editor/index.js",
@@ -261,6 +289,7 @@
261
289
  "class-variance-authority": "^0.7.1",
262
290
  "clsx": "^2.1.1",
263
291
  "cmdk": "^1.1.1",
292
+ "culori": "^4.0.2",
264
293
  "date-fns": "^4.4.0",
265
294
  "embla-carousel-react": "^8.6.0",
266
295
  "highlight.js": "^11.11.1",
@@ -279,6 +308,7 @@
279
308
  "yjs": "^13.6.31"
280
309
  },
281
310
  "devDependencies": {
311
+ "@types/culori": "^4.0.1",
282
312
  "@types/react": "^19.2.14",
283
313
  "@types/react-dom": "^19.2.3",
284
314
  "typescript-7": "npm:typescript@^7.0.2",
@@ -4,6 +4,9 @@ import { act } from "react";
4
4
  import { createRoot, type Root } from "react-dom/client";
5
5
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
6
 
7
+ import { ActionButton, IconButton } from "../design-system/components.js";
8
+ import { defineDesignSystem } from "../design-system/definition.js";
9
+ import { ToolkitProvider } from "../provider.js";
7
10
  import type { ChatHistoryItem } from "./ChatHistoryList.js";
8
11
  import { ChatHistoryRail } from "./ChatHistoryRail.js";
9
12
 
@@ -135,4 +138,175 @@ describe("ChatHistoryRail", () => {
135
138
  act(() => rows[5]?.click());
136
139
  expect(onSelect).toHaveBeenCalledWith("thread-6");
137
140
  });
141
+
142
+ it("lets a design system replace the whole view without replacing its controller", () => {
143
+ const onNewChat = vi.fn();
144
+ const CustomActionButton = vi.fn(
145
+ ({ children, onPress }: Parameters<typeof ActionButton>[0]) => (
146
+ <button
147
+ data-acme-action
148
+ type="button"
149
+ onClick={(event) => onPress?.(event)}
150
+ >
151
+ {children}
152
+ </button>
153
+ ),
154
+ );
155
+ const designSystem = defineDesignSystem({
156
+ name: "Acme",
157
+ components: { ActionButton: CustomActionButton },
158
+ });
159
+
160
+ act(() => {
161
+ root.render(
162
+ <ToolkitProvider designSystem={designSystem}>
163
+ <ChatHistoryRail
164
+ items={makeItems(20)}
165
+ onSelect={() => {}}
166
+ onNewChat={onNewChat}
167
+ railLabels={railLabels}
168
+ renderRail={({ controller }) => (
169
+ <section data-acme-rail>
170
+ <output>{controller.visibleItems.length}</output>
171
+ <ActionButton onPress={controller.onNewChat}>
172
+ {controller.newChatLabel}
173
+ </ActionButton>
174
+ <ActionButton onPress={controller.toggleExpanded}>
175
+ {controller.disclosureLabel}
176
+ </ActionButton>
177
+ </section>
178
+ )}
179
+ />
180
+ </ToolkitProvider>,
181
+ );
182
+ });
183
+
184
+ const actions =
185
+ container.querySelectorAll<HTMLButtonElement>("[data-acme-action]");
186
+ expect(container.querySelector("[data-acme-rail]")).not.toBeNull();
187
+ expect(container.querySelector("output")?.textContent).toBe("5");
188
+ expect(actions[1]?.textContent).toBe("Show more chats");
189
+
190
+ act(() => actions[1]?.click());
191
+ expect(container.querySelector("output")?.textContent).toBe("15");
192
+ expect(actions[1]?.textContent).toBe("Show fewer chats");
193
+
194
+ act(() => actions[0]?.click());
195
+ expect(onNewChat).toHaveBeenCalledOnce();
196
+ expect(CustomActionButton).toHaveBeenCalled();
197
+ });
198
+
199
+ it("routes the default footer through the semantic action bridge", () => {
200
+ const onNewChat = vi.fn();
201
+ const CustomActionButton = vi.fn(
202
+ ({ children, onPress }: Parameters<typeof ActionButton>[0]) => (
203
+ <button
204
+ data-semantic-action
205
+ type="button"
206
+ onClick={(event) => onPress?.(event)}
207
+ >
208
+ {children}
209
+ </button>
210
+ ),
211
+ );
212
+ const CustomIconButton = vi.fn(
213
+ ({ icon, label, onPress }: Parameters<typeof IconButton>[0]) => (
214
+ <button
215
+ data-semantic-icon
216
+ type="button"
217
+ aria-label={label}
218
+ onClick={(event) => onPress?.(event)}
219
+ >
220
+ {icon}
221
+ </button>
222
+ ),
223
+ );
224
+ const designSystem = defineDesignSystem({
225
+ name: "Acme",
226
+ components: {
227
+ ActionButton: CustomActionButton,
228
+ IconButton: CustomIconButton,
229
+ },
230
+ });
231
+
232
+ act(() => {
233
+ root.render(
234
+ <ToolkitProvider designSystem={designSystem}>
235
+ <ChatHistoryRail
236
+ items={makeItems(6)}
237
+ onSelect={() => {}}
238
+ onNewChat={onNewChat}
239
+ railLabels={railLabels}
240
+ />
241
+ </ToolkitProvider>,
242
+ );
243
+ });
244
+
245
+ const newChat = container.querySelector<HTMLButtonElement>(
246
+ "[data-semantic-action]",
247
+ );
248
+ const disclosure = container.querySelector<HTMLButtonElement>(
249
+ "[data-semantic-icon]",
250
+ );
251
+ expect(newChat).not.toBeNull();
252
+ expect(disclosure?.getAttribute("aria-label")).toBe("Show more chats");
253
+ expect(CustomActionButton.mock.calls[0]?.[0]).toMatchObject({
254
+ emphasis: "ghost",
255
+ size: "compact",
256
+ leadingIcon: expect.anything(),
257
+ });
258
+ expect(CustomIconButton.mock.calls[0]?.[0]).toMatchObject({
259
+ size: "compact",
260
+ "aria-expanded": false,
261
+ label: "Show more chats",
262
+ });
263
+
264
+ act(() => newChat?.click());
265
+ expect(onNewChat).toHaveBeenCalledOnce();
266
+ });
267
+
268
+ it("keeps native button semantics and focus in the default view", () => {
269
+ const onNewChat = vi.fn();
270
+ act(() => {
271
+ root.render(
272
+ <ChatHistoryRail
273
+ items={makeItems(6)}
274
+ onSelect={() => {}}
275
+ onNewChat={onNewChat}
276
+ railLabels={railLabels}
277
+ />,
278
+ );
279
+ });
280
+
281
+ const newChat = container.querySelector<HTMLButtonElement>(
282
+ ".an-chat-history-rail__new-chat",
283
+ );
284
+ newChat?.focus();
285
+ act(() => newChat?.click());
286
+
287
+ expect(newChat?.type).toBe("button");
288
+ expect(document.activeElement).toBe(newChat);
289
+ expect(onNewChat).toHaveBeenCalledOnce();
290
+ });
291
+
292
+ it("falls back to the default rail when a product renderer fails", () => {
293
+ vi.spyOn(console, "error").mockImplementation(() => {});
294
+ act(() => {
295
+ root.render(
296
+ <ChatHistoryRail
297
+ items={makeItems(6)}
298
+ onSelect={() => {}}
299
+ onNewChat={() => {}}
300
+ railLabels={railLabels}
301
+ renderRail={() => {
302
+ throw new Error("broken company rail");
303
+ }}
304
+ />,
305
+ );
306
+ });
307
+
308
+ expect(
309
+ container.querySelector(".an-chat-history-rail__new-chat"),
310
+ ).not.toBeNull();
311
+ });
138
312
  });
@@ -1,11 +1,17 @@
1
1
  import { IconDots, IconPlus } from "@tabler/icons-react";
2
- import { useEffect, useMemo, useState } from "react";
2
+ import type { ReactNode } from "react";
3
3
 
4
+ import { ActionButton, IconButton } from "../design-system/components.js";
5
+ import { DesignSystemErrorBoundary } from "../design-system/error-boundary.js";
4
6
  import {
5
7
  ChatHistoryList,
6
8
  type ChatHistoryItem,
7
9
  type ChatHistoryListProps,
8
10
  } from "./ChatHistoryList.js";
11
+ import {
12
+ type ChatHistoryRailController,
13
+ useChatHistoryRailController,
14
+ } from "./useChatHistoryRailController.js";
9
15
 
10
16
  export interface ChatHistoryRailLabels {
11
17
  newChat: string;
@@ -22,63 +28,71 @@ export interface ChatHistoryRailProps extends Omit<
22
28
  railLabels: ChatHistoryRailLabels;
23
29
  previewCount?: number;
24
30
  expandedCount?: number;
31
+ /**
32
+ * Product-level presentation slot for design systems that want to replace
33
+ * the rail wholesale. State and actions still come from the same controller
34
+ * as the default view.
35
+ */
36
+ renderRail?: (context: ChatHistoryRailRenderContext) => ReactNode;
25
37
  }
26
38
 
27
- /**
28
- * Compact recent-chat rail for app sidebars. Hosts own thread persistence,
29
- * sorting, routing, and mutations; the rail only owns progressive disclosure.
30
- */
31
- export function ChatHistoryRail({
32
- items,
33
- onNewChat,
34
- railLabels,
35
- previewCount = 5,
36
- expandedCount = 15,
37
- className,
38
- emptyLabel,
39
- ...listProps
40
- }: ChatHistoryRailProps) {
41
- const [expanded, setExpanded] = useState(false);
42
- const collapsedLimit = Math.max(1, previewCount);
43
- const expandedLimit = Math.max(collapsedLimit, expandedCount);
44
- const canExpand = items.length > collapsedLimit;
45
- const visibleItems = useMemo(
46
- () => items.slice(0, expanded ? expandedLimit : collapsedLimit),
47
- [collapsedLimit, expanded, expandedLimit, items],
48
- );
39
+ export interface ChatHistoryRailRenderContext {
40
+ controller: ChatHistoryRailController<ChatHistoryItem>;
41
+ listProps: Omit<
42
+ ChatHistoryListProps,
43
+ "footer" | "items" | "sections" | "variant"
44
+ >;
45
+ }
49
46
 
50
- useEffect(() => {
51
- if (!canExpand) setExpanded(false);
52
- }, [canExpand]);
47
+ export type DefaultChatHistoryRailViewProps = ChatHistoryRailRenderContext;
48
+
49
+ export function DefaultChatHistoryRailView({
50
+ controller,
51
+ listProps,
52
+ }: ChatHistoryRailRenderContext) {
53
+ const {
54
+ canExpand,
55
+ disclosureLabel,
56
+ expanded,
57
+ newChatLabel,
58
+ onNewChat,
59
+ toggleExpanded,
60
+ visibleItems,
61
+ } = controller;
62
+ const { className, emptyLabel, ...rest } = listProps;
53
63
 
54
64
  const footer = (
55
65
  <div className="an-chat-history-rail__footer">
56
- <button
66
+ <ActionButton
57
67
  type="button"
58
68
  className="an-chat-history-rail__new-chat"
59
- onClick={onNewChat}
69
+ emphasis="ghost"
70
+ size="compact"
71
+ leadingIcon={
72
+ <IconPlus size={13} strokeWidth={1.8} aria-hidden="true" />
73
+ }
74
+ onPress={onNewChat}
60
75
  >
61
- <IconPlus size={13} strokeWidth={1.8} aria-hidden="true" />
62
- <span>{railLabels.newChat}</span>
63
- </button>
76
+ <span>{newChatLabel}</span>
77
+ </ActionButton>
64
78
  {canExpand && (
65
- <button
79
+ <IconButton
66
80
  type="button"
67
81
  className="an-chat-history-rail__disclosure"
68
- onClick={() => setExpanded((current) => !current)}
82
+ size="compact"
83
+ icon={<IconDots size={14} strokeWidth={1.8} aria-hidden="true" />}
84
+ onPress={toggleExpanded}
69
85
  aria-expanded={expanded}
70
- aria-label={expanded ? railLabels.showLess : railLabels.showMore}
71
- title={expanded ? railLabels.showLess : railLabels.showMore}
72
- >
73
- <IconDots size={14} strokeWidth={1.8} aria-hidden="true" />
74
- </button>
86
+ label={disclosureLabel}
87
+ title={disclosureLabel}
88
+ />
75
89
  )}
76
90
  </div>
77
91
  );
78
92
 
79
93
  return (
80
94
  <ChatHistoryList
81
- {...listProps}
95
+ {...rest}
82
96
  items={visibleItems}
83
97
  footer={footer}
84
98
  emptyLabel={emptyLabel ?? null}
@@ -87,3 +101,50 @@ export function ChatHistoryRail({
87
101
  />
88
102
  );
89
103
  }
104
+
105
+ /**
106
+ * Compact recent-chat rail for app sidebars. Hosts own thread persistence,
107
+ * sorting, routing, and mutations; the rail only owns progressive disclosure.
108
+ */
109
+ export function ChatHistoryRail({
110
+ items,
111
+ onNewChat,
112
+ railLabels,
113
+ previewCount = 5,
114
+ expandedCount = 15,
115
+ renderRail,
116
+ className,
117
+ emptyLabel,
118
+ ...listProps
119
+ }: ChatHistoryRailProps) {
120
+ const controller = useChatHistoryRailController({
121
+ items,
122
+ onNewChat,
123
+ labels: railLabels,
124
+ previewCount,
125
+ expandedCount,
126
+ });
127
+ const context: ChatHistoryRailRenderContext = {
128
+ controller,
129
+ listProps: { ...listProps, className, emptyLabel },
130
+ };
131
+ const fallback = <DefaultChatHistoryRailView {...context} />;
132
+
133
+ return renderRail ? (
134
+ <DesignSystemErrorBoundary component="ChatHistoryRail" fallback={fallback}>
135
+ <ChatHistoryRailCustomView renderRail={renderRail} context={context} />
136
+ </DesignSystemErrorBoundary>
137
+ ) : (
138
+ fallback
139
+ );
140
+ }
141
+
142
+ function ChatHistoryRailCustomView({
143
+ renderRail,
144
+ context,
145
+ }: {
146
+ renderRail: NonNullable<ChatHistoryRailProps["renderRail"]>;
147
+ context: ChatHistoryRailRenderContext;
148
+ }) {
149
+ return renderRail(context);
150
+ }
@@ -7,6 +7,15 @@ export {
7
7
  } from "./ChatHistoryList.js";
8
8
  export {
9
9
  ChatHistoryRail,
10
+ DefaultChatHistoryRailView,
11
+ type DefaultChatHistoryRailViewProps,
10
12
  type ChatHistoryRailLabels,
11
13
  type ChatHistoryRailProps,
14
+ type ChatHistoryRailRenderContext,
12
15
  } from "./ChatHistoryRail.js";
16
+ export {
17
+ type ChatHistoryRailController,
18
+ type ChatHistoryRailControllerLabels,
19
+ type UseChatHistoryRailControllerOptions,
20
+ useChatHistoryRailController,
21
+ } from "./useChatHistoryRailController.js";
@@ -0,0 +1,131 @@
1
+ // @vitest-environment happy-dom
2
+
3
+ import { act } from "react";
4
+ import { createRoot, type Root } from "react-dom/client";
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
+
7
+ import {
8
+ type ChatHistoryRailController,
9
+ type UseChatHistoryRailControllerOptions,
10
+ useChatHistoryRailController,
11
+ } from "./useChatHistoryRailController.js";
12
+
13
+ interface Item {
14
+ id: string;
15
+ }
16
+
17
+ const labels = {
18
+ newChat: "New chat",
19
+ showMore: "Show more chats",
20
+ showLess: "Show fewer chats",
21
+ };
22
+
23
+ function makeItems(count: number): Item[] {
24
+ return Array.from({ length: count }, (_, index) => ({
25
+ id: `thread-${index + 1}`,
26
+ }));
27
+ }
28
+
29
+ describe("useChatHistoryRailController", () => {
30
+ let container: HTMLDivElement;
31
+ let root: Root;
32
+ let controller: ChatHistoryRailController<Item>;
33
+
34
+ function Harness(props: UseChatHistoryRailControllerOptions<Item>) {
35
+ controller = useChatHistoryRailController(props);
36
+ return null;
37
+ }
38
+
39
+ function render(options: Partial<UseChatHistoryRailControllerOptions<Item>>) {
40
+ const props: UseChatHistoryRailControllerOptions<Item> = {
41
+ items: makeItems(20),
42
+ labels,
43
+ onNewChat: () => {},
44
+ ...options,
45
+ };
46
+ act(() => root.render(<Harness {...props} />));
47
+ }
48
+
49
+ beforeEach(() => {
50
+ vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
51
+ container = document.createElement("div");
52
+ document.body.appendChild(container);
53
+ root = createRoot(container);
54
+ });
55
+
56
+ afterEach(() => {
57
+ act(() => root.unmount());
58
+ container.remove();
59
+ vi.restoreAllMocks();
60
+ vi.unstubAllGlobals();
61
+ });
62
+
63
+ it("progressively discloses the default five and fifteen item limits", () => {
64
+ const items = makeItems(20);
65
+ render({ items });
66
+
67
+ expect(controller.expanded).toBe(false);
68
+ expect(controller.collapsedLimit).toBe(5);
69
+ expect(controller.expandedLimit).toBe(15);
70
+ expect(controller.visibleItems).toEqual(items.slice(0, 5));
71
+ expect(controller.canExpand).toBe(true);
72
+ expect(controller.disclosureLabel).toBe("Show more chats");
73
+
74
+ act(() => controller.toggleExpanded());
75
+
76
+ expect(controller.expanded).toBe(true);
77
+ expect(controller.visibleItems).toEqual(items.slice(0, 15));
78
+ expect(controller.disclosureLabel).toBe("Show fewer chats");
79
+ });
80
+
81
+ it("clamps finite limits while preserving legacy disclosure behavior", () => {
82
+ render({ previewCount: -2.5, expandedCount: 1.9 });
83
+
84
+ expect(controller.collapsedLimit).toBe(1);
85
+ expect(controller.expandedLimit).toBe(1);
86
+ expect(controller.visibleItems).toHaveLength(1);
87
+ expect(controller.canExpand).toBe(true);
88
+
89
+ act(() => controller.toggleExpanded());
90
+ expect(controller.expanded).toBe(true);
91
+ });
92
+
93
+ it("uses defaults for non-finite limits and never puts expanded below preview", () => {
94
+ render({
95
+ previewCount: Number.NaN,
96
+ expandedCount: Number.POSITIVE_INFINITY,
97
+ });
98
+ expect(controller.collapsedLimit).toBe(5);
99
+ expect(controller.expandedLimit).toBe(15);
100
+
101
+ render({ previewCount: 8, expandedCount: 3 });
102
+ expect(controller.collapsedLimit).toBe(8);
103
+ expect(controller.expandedLimit).toBe(8);
104
+ });
105
+
106
+ it("resets disclosure explicitly and when the collection becomes too short", () => {
107
+ render({});
108
+ act(() => controller.toggleExpanded());
109
+ expect(controller.expanded).toBe(true);
110
+
111
+ act(() => controller.resetExpanded());
112
+ expect(controller.expanded).toBe(false);
113
+
114
+ act(() => controller.toggleExpanded());
115
+ expect(controller.expanded).toBe(true);
116
+
117
+ render({ items: makeItems(3) });
118
+ expect(controller.expanded).toBe(false);
119
+ expect(controller.canExpand).toBe(false);
120
+ expect(controller.visibleItems).toHaveLength(3);
121
+ });
122
+
123
+ it("exposes view labels and the host new-chat action without UI assumptions", () => {
124
+ const onNewChat = vi.fn();
125
+ render({ onNewChat });
126
+
127
+ expect(controller.newChatLabel).toBe("New chat");
128
+ act(() => controller.onNewChat());
129
+ expect(onNewChat).toHaveBeenCalledOnce();
130
+ });
131
+ });
@@ -0,0 +1,76 @@
1
+ import { useCallback, useEffect, useMemo, useState } from "react";
2
+
3
+ export interface ChatHistoryRailControllerLabels {
4
+ newChat: string;
5
+ showMore: string;
6
+ showLess: string;
7
+ }
8
+
9
+ export interface UseChatHistoryRailControllerOptions<Item> {
10
+ items: readonly Item[];
11
+ onNewChat: () => void;
12
+ labels: ChatHistoryRailControllerLabels;
13
+ previewCount?: number;
14
+ expandedCount?: number;
15
+ }
16
+
17
+ export interface ChatHistoryRailController<Item> {
18
+ expanded: boolean;
19
+ collapsedLimit: number;
20
+ expandedLimit: number;
21
+ visibleItems: Item[];
22
+ canExpand: boolean;
23
+ newChatLabel: string;
24
+ disclosureLabel: string;
25
+ onNewChat: () => void;
26
+ toggleExpanded: () => void;
27
+ resetExpanded: () => void;
28
+ }
29
+
30
+ function normalizeLimit(value: number | undefined, fallback: number): number {
31
+ if (value === undefined || !Number.isFinite(value)) return fallback;
32
+ return Math.max(1, Math.floor(value));
33
+ }
34
+
35
+ export function useChatHistoryRailController<Item>({
36
+ items,
37
+ onNewChat,
38
+ labels,
39
+ previewCount,
40
+ expandedCount,
41
+ }: UseChatHistoryRailControllerOptions<Item>): ChatHistoryRailController<Item> {
42
+ const [expandedState, setExpandedState] = useState(false);
43
+ const collapsedLimit = normalizeLimit(previewCount, 5);
44
+ const expandedLimit = Math.max(
45
+ collapsedLimit,
46
+ normalizeLimit(expandedCount, 15),
47
+ );
48
+ const canExpand = items.length > collapsedLimit;
49
+ const expanded = canExpand && expandedState;
50
+ const visibleItems = useMemo(
51
+ () => items.slice(0, expanded ? expandedLimit : collapsedLimit),
52
+ [collapsedLimit, expanded, expandedLimit, items],
53
+ );
54
+
55
+ useEffect(() => {
56
+ if (!canExpand) setExpandedState(false);
57
+ }, [canExpand]);
58
+
59
+ const toggleExpanded = useCallback(() => {
60
+ if (canExpand) setExpandedState((current) => !current);
61
+ }, [canExpand]);
62
+ const resetExpanded = useCallback(() => setExpandedState(false), []);
63
+
64
+ return {
65
+ expanded,
66
+ collapsedLimit,
67
+ expandedLimit,
68
+ visibleItems,
69
+ canExpand,
70
+ newChatLabel: labels.newChat,
71
+ disclosureLabel: expanded ? labels.showLess : labels.showMore,
72
+ onNewChat,
73
+ toggleExpanded,
74
+ resetExpanded,
75
+ };
76
+ }
@@ -113,12 +113,14 @@
113
113
  justify-content: center;
114
114
  flex: 0 0 auto;
115
115
  width: 30px;
116
+ height: auto;
116
117
  min-height: 30px;
117
118
  }
118
119
 
119
120
  .an-chat-history-rail__new-chat {
120
121
  flex: 1 1 auto;
121
122
  gap: 8px;
123
+ height: auto;
122
124
  min-width: 0;
123
125
  min-height: 30px;
124
126
  padding: 6px 10px;