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
package/src/chat/ChatPanel.tsx
CHANGED
|
@@ -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
|
-
|
|
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}
|
package/src/chat/MessageItem.tsx
CHANGED
|
@@ -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
|
+
})
|
package/src/ui/dropdown-menu.tsx
CHANGED
|
@@ -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> & {
|
|
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(
|
package/src/ui/tooltip.tsx
CHANGED
|
@@ -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",
|