Create a floating panel

"""
Isolated test: empty Page + one floating, draggable Paper built as a JSComponent.

    panel serve drag_test.py --show

Drag the ⠿ handle to move the bar. The toggle / stars / Submit stay clickable
because the drag only starts from the handle.
"""
import panel as pn
import panel_material_ui as pmui
from panel.custom import JSComponent, Children

pn.extension()


class FloatingPanel(JSComponent):
    """A fixed, draggable container. Whatever you pass as `objects` is slotted
    in below a drag handle. Positioning is purely client-side."""

    objects = Children()

    _esm = """
export function render({ model, el }) {
  // The component's own root element is the floating, draggable surface.
  Object.assign(el.style, {
    position: 'fixed', left: '50%', bottom: '24px',
    transform: 'translateX(-50%)', zIndex: '1300',
    display: 'flex', flexDirection: 'column', alignItems: 'center',
    width: 'fit-content', height: 'fit-content',
    padding: '6px 12px', borderRadius: '10px',
    background: 'var(--mui-palette-background-paper, #fff)',
    boxShadow: '0 6px 24px rgba(0,0,0,.25)', userSelect: 'none', cursor: 'grab',
  });

  // slotted Panel children (toggle / rating / button …)
  for (const child of model.get_child('objects')) el.appendChild(child);

  // drag from the panel background — clicks on the slotted widgets pass through
  // (their target isn't `el` itself, so they stay interactive)
  let on = false, sx, sy, baseL, baseT;
  el.addEventListener('pointerdown', e => {
    if (e.target !== el) return;             // only the surface/padding drags
    const r = el.getBoundingClientRect();
    el.style.transform = 'none'; el.style.bottom = 'auto';
    el.style.left = r.left + 'px'; el.style.top = r.top + 'px';
    baseL = r.left; baseT = r.top; sx = e.clientX; sy = e.clientY;
    on = true; el.setPointerCapture(e.pointerId); el.style.cursor = 'grabbing';
    e.preventDefault();
  });
  el.addEventListener('pointermove', e => {
    if (!on) return;
    el.style.left = (baseL + e.clientX - sx) + 'px';
    el.style.top  = (baseT + e.clientY - sy) + 'px';
  });
  el.addEventListener('pointerup', () => { on = false; el.style.cursor = 'grab'; });
}
"""


toggle = pmui.ToggleIcon(icon="thumb_down", active_icon="thumb_up", margin=0)
rating = pmui.Rating(value=5, size="small", margin=0)
button = pmui.Button(label="Submit", size="small", margin=(0, 5, 5, 5))

controls = FloatingPanel(objects=[pmui.Row(toggle, rating), button])

pmui.Page(title="Drag test", main=[controls]).show()