canopy-ui 0.12.0 → 0.12.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canopy-ui",
3
- "version": "0.12.0",
3
+ "version": "0.12.2",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -179,7 +179,10 @@ export function ChatPanel({
179
179
  header, i.e. unreachable. Pinning keeps "Load earlier"/"Load full"
180
180
  always visible. */}
181
181
  {historySlot}
182
- <div ref={containerRef} onScroll={onScroll} className="flex-1 overflow-y-auto">
182
+ {/* overflow-x-hidden: `overflow-y: auto` alone computes overflow-x to
183
+ auto, so anything wider than the column made the transcript scroll
184
+ sideways into empty space. Wide content scrolls inside its own box. */}
185
+ <div ref={containerRef} onScroll={onScroll} className="flex-1 overflow-x-hidden overflow-y-auto">
183
186
  <MessageList
184
187
  messages={state.messages}
185
188
  emptyState={emptyState}
@@ -119,8 +119,13 @@ export function MessageItem({
119
119
  const showThinking =
120
120
  (isPending || isStreaming) && message.role === "assistant" && !text;
121
121
  return (
122
+ // `overflow-wrap:anywhere` so one unbreakable token — a long path, a URL,
123
+ // inline code — wraps inside the bubble. Without it the token overflowed
124
+ // `max-w-[80%]` and made the whole transcript scroll sideways into empty
125
+ // space on a phone (2026-09-24). Tables and code blocks keep their own
126
+ // horizontal scroll; this only affects running text.
122
127
  <div
123
- className={`my-2 max-w-[80%] rounded-2xl px-4 py-2 ${bubbleClass}`}
128
+ className={`my-2 min-w-0 max-w-[80%] rounded-2xl px-4 py-2 [overflow-wrap:anywhere] ${bubbleClass}`}
124
129
  aria-live={isStreaming || isPending ? "polite" : undefined}
125
130
  >
126
131
  {showThinking ? (
@@ -0,0 +1,26 @@
1
+ // @vitest-environment jsdom
2
+ import { render } from "@testing-library/react"
3
+ import { describe, expect, it } from "vitest"
4
+
5
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "./dropdown-menu"
6
+
7
+ /** The z-index belongs on the POSITIONER. Base UI's positioner is the placed
8
+ * element and forms its own stacking context, so a z-index on the popup alone
9
+ * let the chat's sticky `z-10` bar paint over the menu and swallow the taps
10
+ * meant for its items (2026-09-23). jsdom cannot compute stacking, so this
11
+ * pins the class that makes it right. */
12
+ describe("DropdownMenuContent", () => {
13
+ it("raises the positioner itself, not only the popup inside it", () => {
14
+ render(
15
+ <DropdownMenu open>
16
+ <DropdownMenuTrigger>open</DropdownMenuTrigger>
17
+ <DropdownMenuContent>
18
+ <DropdownMenuItem>Share to Slack…</DropdownMenuItem>
19
+ </DropdownMenuContent>
20
+ </DropdownMenu>,
21
+ )
22
+ const popup = document.querySelector('[data-slot="dropdown-menu-content"]')!
23
+ expect(popup).toBeTruthy()
24
+ expect(popup.parentElement!.className).toContain("z-50")
25
+ })
26
+ })
@@ -21,14 +21,26 @@ function DropdownMenuTrigger({
21
21
  )
22
22
  }
23
23
 
24
+ // The z-index goes on the POSITIONER, not only the popup. Base UI's positioner
25
+ // is the element that is actually placed, and it forms its own stacking
26
+ // context, so a `z-50` on the popup inside it is ranked only against the
27
+ // positioner's own children. The menu then painted UNDER any positioned
28
+ // z-index on the page (the chat's sticky `z-10` tool-call bar) and those items
29
+ // could not be tapped (2026-09-23, the chat session menu on a phone).
30
+ const POSITIONER_Z = "z-50"
31
+
24
32
  function DropdownMenuContent({
25
33
  className,
26
34
  sideOffset = 4,
35
+ align,
27
36
  ...props
28
- }: React.ComponentProps<typeof Menu.Popup> & { sideOffset?: number; align?: string }) {
37
+ }: React.ComponentProps<typeof Menu.Popup> & {
38
+ sideOffset?: number
39
+ align?: "start" | "center" | "end"
40
+ }) {
29
41
  return (
30
42
  <Menu.Portal>
31
- <Menu.Positioner sideOffset={sideOffset}>
43
+ <Menu.Positioner className={POSITIONER_Z} sideOffset={sideOffset} align={align}>
32
44
  <Menu.Popup
33
45
  data-slot="dropdown-menu-content"
34
46
  className={cn(
@@ -191,7 +203,7 @@ function DropdownMenuSubContent({
191
203
  }: React.ComponentProps<typeof Menu.Popup>) {
192
204
  return (
193
205
  <Menu.Portal>
194
- <Menu.Positioner>
206
+ <Menu.Positioner className={POSITIONER_Z}>
195
207
  <Menu.Popup
196
208
  data-slot="dropdown-menu-sub-content"
197
209
  className={cn(
@@ -37,7 +37,7 @@ function Tooltip({ label, children, delay = 300, side = "top", sideOffset = 6 }:
37
37
  <TooltipPrimitive.Root>
38
38
  <TooltipPrimitive.Trigger delay={delay} render={children} />
39
39
  <TooltipPrimitive.Portal>
40
- <TooltipPrimitive.Positioner side={side} sideOffset={sideOffset}>
40
+ <TooltipPrimitive.Positioner className="z-50" side={side} sideOffset={sideOffset}>
41
41
  <TooltipPrimitive.Popup
42
42
  className={cn(
43
43
  "z-50 max-w-xs rounded-md border border-border bg-popover px-2 py-1 text-xs text-popover-foreground shadow-md",