11import { compactHomePath } from "@posthog/shared" ;
22import { Box , Flex , Text } from "@radix-ui/themes" ;
3- import { useCallback , useEffect , useRef } from "react" ;
3+ import { useCallback , useEffect , useRef , useState } from "react" ;
44import { isCancelOption , isSubmitOption } from "./constants" ;
55import { OptionRow } from "./OptionRow" ;
66import { StepTabs } from "./StepTabs" ;
77import type { ActionSelectorProps } from "./types" ;
88import { useActionSelectorState } from "./useActionSelectorState" ;
99
10+ // Floor keeps the options and submit row visible even at the smallest size;
11+ // ceiling matches the card's default `max-h-[80vh]` cap.
12+ const MIN_CARD_HEIGHT = 160 ;
13+ const MAX_CARD_HEIGHT_FRACTION = 0.8 ;
14+
1015export function ActionSelector ( {
1116 title,
1217 pendingAction,
@@ -20,6 +25,7 @@ export function ActionSelector({
2025 initialSelections,
2126 initialCustomInput,
2227 hideSubmitButton = false ,
28+ resizable = false ,
2329 onSelect,
2430 onMultiSelect,
2531 onCancel,
@@ -76,6 +82,68 @@ export function ActionSelector({
7682 onCancel ?.( ) ;
7783 } , [ onCancel ] ) ;
7884
85+ // User-chosen height in px once the card has been dragged; null means the
86+ // card sizes naturally under its `max-h-[80vh]` cap.
87+ const [ cardHeight , setCardHeight ] = useState < number | null > ( null ) ;
88+ const [ isResizing , setIsResizing ] = useState ( false ) ;
89+ const resizeStartRef = useRef ( { y : 0 , height : 0 } ) ;
90+
91+ const handleResizeMouseDown = useCallback (
92+ ( e : React . MouseEvent ) => {
93+ const container = containerRef . current ;
94+ if ( ! container ) return ;
95+ e . preventDefault ( ) ;
96+ resizeStartRef . current = {
97+ y : e . clientY ,
98+ height : container . getBoundingClientRect ( ) . height ,
99+ } ;
100+ setIsResizing ( true ) ;
101+ document . body . style . cursor = "row-resize" ;
102+ document . body . style . userSelect = "none" ;
103+ } ,
104+ [ containerRef ] ,
105+ ) ;
106+
107+ useEffect ( ( ) => {
108+ if ( ! isResizing ) return ;
109+
110+ const handleMouseMove = ( e : MouseEvent ) => {
111+ const { y, height } = resizeStartRef . current ;
112+ // Dragging up (clientY decreases) grows the card; down shrinks it,
113+ // revealing more of the transcript above.
114+ const next = height + ( y - e . clientY ) ;
115+ const max = window . innerHeight * MAX_CARD_HEIGHT_FRACTION ;
116+ setCardHeight ( Math . max ( MIN_CARD_HEIGHT , Math . min ( max , next ) ) ) ;
117+ } ;
118+
119+ const handleMouseUp = ( ) => {
120+ setIsResizing ( false ) ;
121+ document . body . style . cursor = "" ;
122+ document . body . style . userSelect = "" ;
123+ } ;
124+
125+ document . addEventListener ( "mousemove" , handleMouseMove ) ;
126+ document . addEventListener ( "mouseup" , handleMouseUp ) ;
127+ return ( ) => {
128+ document . removeEventListener ( "mousemove" , handleMouseMove ) ;
129+ document . removeEventListener ( "mouseup" , handleMouseUp ) ;
130+ } ;
131+ } , [ isResizing ] ) ;
132+
133+ // If the card unmounts mid-drag no mouseup fires — clear the global cursor
134+ // and text-selection lock so the app isn't left stuck.
135+ const isResizingRef = useRef ( isResizing ) ;
136+ isResizingRef . current = isResizing ;
137+ useEffect (
138+ ( ) => ( ) => {
139+ if ( isResizingRef . current ) {
140+ document . body . style . cursor = "" ;
141+ document . body . style . userSelect = "" ;
142+ }
143+ } ,
144+ [ ] ,
145+ ) ;
146+
79147 const handlersRef = useRef ( {
80148 moveUp,
81149 moveDown,
@@ -226,9 +294,32 @@ export function ActionSelector({
226294 } }
227295 style = { {
228296 outline : "none" ,
297+ ...( resizable && cardHeight !== null ? { height : cardHeight } : { } ) ,
229298 } }
230- className = "flex max-h-[80vh] flex-col rounded-(--radius-3) border border-(--gray-6) bg-(--gray-1)"
299+ className = "relative flex max-h-[80vh] flex-col rounded-(--radius-3) border border-(--gray-6) bg-(--gray-1)"
231300 >
301+ { resizable && (
302+ // Drag handle riding the top edge — the card is anchored to the bottom
303+ // of the chat, so dragging up grows it and dragging down shrinks it.
304+ < Box
305+ aria-hidden
306+ onMouseDown = { handleResizeMouseDown }
307+ className = "group absolute inset-x-0 top-0 z-10 flex h-2 cursor-row-resize items-start justify-center"
308+ >
309+ < span
310+ className = { `mt-0.5 h-1 w-10 rounded-full transition-colors ${
311+ isResizing
312+ ? "bg-(--gray-8)"
313+ : "bg-(--gray-6) group-hover:bg-(--gray-8)"
314+ } `}
315+ />
316+ </ Box >
317+ ) }
318+ { isResizing && (
319+ // Keeps the row-resize cursor while the pointer crosses content that
320+ // sets its own cursor.
321+ < Box className = "fixed inset-0 z-[200] cursor-row-resize" />
322+ ) }
232323 < Flex direction = "column" gap = "2" className = "min-h-0 flex-1" >
233324 { hasSteps && steps && (
234325 < StepTabs
0 commit comments