mirror of
https://github.com/block/buzz.git
synced 2026-10-29 05:21:31 +00:00
feat(desktop): resize merged message panels
Signed-off-by: cynfria <yescynthia@gmail.com>
This commit is contained in:
@@ -1014,8 +1014,9 @@ function BestieChat({ onClose }: { onClose: () => void }) {
|
||||
const [position, setPosition] = useState({ x: 0, y: 0 });
|
||||
const [mergedEdge, setMergedEdge] = useState<MessagePanelEdge | null>(null);
|
||||
const [mergedToStickyPanel, setMergedToStickyPanel] = useState(false);
|
||||
const [slotSize, setSlotSize] = useState<{ width: number; height: number } | null>(null);
|
||||
const panelRef = useRef<HTMLElement>(null);
|
||||
const mergedTargetRef = useRef<{ cell: HTMLElement; edge: MessagePanelEdge } | null>(null);
|
||||
const mergedTargetRef = useRef<{ cell: HTMLElement; edge: MessagePanelEdge; slotted: boolean } | null>(null);
|
||||
const dragRef = useRef<{
|
||||
pointerId: number;
|
||||
startX: number;
|
||||
@@ -1046,6 +1047,9 @@ function BestieChat({ onClose }: { onClose: () => void }) {
|
||||
const merged = mergedTargetRef.current;
|
||||
if (!merged) return;
|
||||
merged.cell.classList.remove(`bestie-merge-target-${merged.edge}`);
|
||||
merged.cell.classList.remove("bestie-is-slotted");
|
||||
merged.cell.style.removeProperty("--bestie-slot-width");
|
||||
merged.cell.style.removeProperty("--bestie-slot-height");
|
||||
mergedTargetRef.current = null;
|
||||
};
|
||||
|
||||
@@ -1058,28 +1062,30 @@ function BestieChat({ onClose }: { onClose: () => void }) {
|
||||
const panel = panelRef.current;
|
||||
if (!panel) return null;
|
||||
const dragged = panel.getBoundingClientRect();
|
||||
const candidates: Array<{ cell: HTMLElement; edge: MessagePanelEdge; distance: number; x: number; y: number }> = [];
|
||||
const candidates: Array<{ cell: HTMLElement; edge: MessagePanelEdge; distance: number; x: number; y: number; targetRect: { left: number; right: number; top: number; bottom: number; width: number; height: number } }> = [];
|
||||
document.querySelectorAll<HTMLElement>(".message-panel-cell[data-panel-id]").forEach((cell) => {
|
||||
const target = cell.querySelector<HTMLElement>(".message-panel");
|
||||
if (!target) return;
|
||||
const rect = target.getBoundingClientRect();
|
||||
const rowAlignment = Math.abs(dragged.top - rect.top);
|
||||
const columnAlignment = Math.abs(dragged.left - rect.left);
|
||||
const targetRect = { left: rect.left, right: rect.right, top: rect.top, bottom: rect.bottom, width: rect.width, height: rect.height };
|
||||
const rowAlignment = Math.min(Math.abs(dragged.top - rect.top), Math.abs(dragged.bottom - rect.bottom));
|
||||
const columnAlignment = Math.min(Math.abs(dragged.left - rect.left), Math.abs(dragged.right - rect.right));
|
||||
const draggedCenterX = (dragged.left + dragged.right) / 2;
|
||||
const draggedCenterY = (dragged.top + dragged.bottom) / 2;
|
||||
const targetCenterX = (rect.left + rect.right) / 2;
|
||||
const targetCenterY = (rect.top + rect.bottom) / 2;
|
||||
const canSlotInside = cell.dataset.panelId !== "sidebar";
|
||||
if (rowAlignment <= 68) {
|
||||
const edge: MessagePanelEdge = draggedCenterX < targetCenterX ? "left" : "right";
|
||||
candidates.push(edge === "left"
|
||||
? { cell, edge, distance: Math.abs(dragged.right - rect.left), x: rect.left - dragged.width, y: rect.top }
|
||||
: { cell, edge, distance: Math.abs(dragged.left - rect.right), x: rect.right, y: rect.top });
|
||||
? { cell, edge, distance: Math.min(Math.abs(dragged.right - rect.left), canSlotInside ? Math.abs(dragged.left - rect.left) : Number.POSITIVE_INFINITY), x: rect.left - dragged.width, y: rect.top, targetRect }
|
||||
: { cell, edge, distance: Math.min(Math.abs(dragged.left - rect.right), canSlotInside ? Math.abs(dragged.right - rect.right) : Number.POSITIVE_INFINITY), x: rect.right, y: rect.top, targetRect });
|
||||
}
|
||||
if (columnAlignment <= 68) {
|
||||
const edge: MessagePanelEdge = draggedCenterY < targetCenterY ? "top" : "bottom";
|
||||
candidates.push(edge === "top"
|
||||
? { cell, edge, distance: Math.abs(dragged.bottom - rect.top), x: rect.left, y: rect.top - dragged.height }
|
||||
: { cell, edge, distance: Math.abs(dragged.top - rect.bottom), x: rect.left, y: rect.bottom });
|
||||
? { cell, edge, distance: Math.min(Math.abs(dragged.bottom - rect.top), canSlotInside ? Math.abs(dragged.top - rect.top) : Number.POSITIVE_INFINITY), x: rect.left, y: rect.top - dragged.height, targetRect }
|
||||
: { cell, edge, distance: Math.min(Math.abs(dragged.top - rect.bottom), canSlotInside ? Math.abs(dragged.bottom - rect.bottom) : Number.POSITIVE_INFINITY), x: rect.left, y: rect.bottom, targetRect });
|
||||
}
|
||||
});
|
||||
const best = candidates.sort((a, b) => a.distance - b.distance)[0];
|
||||
@@ -1135,11 +1141,16 @@ function BestieChat({ onClose }: { onClose: () => void }) {
|
||||
const drag = dragRef.current;
|
||||
const panel = panelRef.current;
|
||||
if (!panel || drag.pointerId !== event.pointerId) return;
|
||||
if (event.buttons === 0) {
|
||||
endDrag(event);
|
||||
return;
|
||||
}
|
||||
const pullDistance = Math.hypot(event.clientX - drag.startX, event.clientY - drag.startY);
|
||||
if (drag.connectedAtStart && pullDistance > 112) {
|
||||
drag.hasBroken = true;
|
||||
removeMergedTarget();
|
||||
setMergedEdge(null);
|
||||
setSlotSize(null);
|
||||
if (mergedToStickyPanel) {
|
||||
drag.originX -= drag.shellPanX;
|
||||
drag.originY -= drag.shellPanY;
|
||||
@@ -1181,17 +1192,39 @@ function BestieChat({ onClose }: { onClose: () => void }) {
|
||||
setDetached(true);
|
||||
if (snap) {
|
||||
removeMergedTarget();
|
||||
const targetIsSidebar = snap.cell.dataset.panelId === "sidebar";
|
||||
const horizontal = snap.edge === "left" || snap.edge === "right";
|
||||
const slotted = !targetIsSidebar;
|
||||
const nextSlotSize = slotted
|
||||
? horizontal
|
||||
? { width: Math.max(220, Math.min(300, snap.targetRect.width * .42)), height: Math.min(430, snap.targetRect.height) }
|
||||
: { width: snap.targetRect.width, height: Math.max(220, Math.min(300, snap.targetRect.height * .36)) }
|
||||
: null;
|
||||
if (nextSlotSize) {
|
||||
snap.cell.classList.add("bestie-is-slotted");
|
||||
snap.cell.style.setProperty("--bestie-slot-width", `${nextSlotSize.width}px`);
|
||||
snap.cell.style.setProperty("--bestie-slot-height", `${nextSlotSize.height}px`);
|
||||
}
|
||||
snap.cell.classList.add(`bestie-merge-target-${snap.edge}`);
|
||||
mergedTargetRef.current = { cell: snap.cell, edge: snap.edge };
|
||||
mergedTargetRef.current = { cell: snap.cell, edge: snap.edge, slotted };
|
||||
setMergedEdge(snap.edge);
|
||||
setMergedToStickyPanel(snap.cell.dataset.panelId === "sidebar");
|
||||
setMergedToStickyPanel(targetIsSidebar);
|
||||
setSlotSize(nextSlotSize);
|
||||
}
|
||||
const next = stayedConnected
|
||||
? { x: drag.originX, y: drag.originY }
|
||||
: snap
|
||||
? snap.cell.dataset.panelId === "sidebar"
|
||||
? { x: snap.x, y: snap.y }
|
||||
: { x: snap.x - drag.shellPanX, y: snap.y - drag.shellPanY }
|
||||
: slotSize || snap.cell.classList.contains("bestie-is-slotted")
|
||||
? snap.edge === "left"
|
||||
? { x: snap.targetRect.left - drag.shellPanX, y: snap.targetRect.top - drag.shellPanY }
|
||||
: snap.edge === "right"
|
||||
? { x: snap.targetRect.right - (Number.parseFloat(snap.cell.style.getPropertyValue("--bestie-slot-width")) || panel.offsetWidth) - drag.shellPanX, y: snap.targetRect.top - drag.shellPanY }
|
||||
: snap.edge === "top"
|
||||
? { x: snap.targetRect.left - drag.shellPanX, y: snap.targetRect.top - drag.shellPanY }
|
||||
: { x: snap.targetRect.left - drag.shellPanX, y: snap.targetRect.bottom - (Number.parseFloat(snap.cell.style.getPropertyValue("--bestie-slot-height")) || panel.offsetHeight) - drag.shellPanY }
|
||||
: { x: snap.x - drag.shellPanX, y: snap.y - drag.shellPanY }
|
||||
: { x: drag.x, y: drag.y };
|
||||
panel.style.setProperty("--bestie-x", `${next.x}px`);
|
||||
panel.style.setProperty("--bestie-y", `${next.y}px`);
|
||||
@@ -1208,16 +1241,17 @@ function BestieChat({ onClose }: { onClose: () => void }) {
|
||||
clearPreview();
|
||||
setMergedEdge(null);
|
||||
setMergedToStickyPanel(false);
|
||||
setSlotSize(null);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<aside
|
||||
ref={panelRef}
|
||||
className={`bestie-chat${detached ? " is-detached" : ""}${mergedEdge ? ` is-merged-${mergedEdge}` : ""}${mergedToStickyPanel ? " is-merged-sticky" : ""}`}
|
||||
className={`bestie-chat${detached ? " is-detached" : ""}${mergedEdge ? ` is-connected is-merged-${mergedEdge}` : ""}${mergedToStickyPanel ? " is-merged-sticky" : ""}${slotSize ? " is-slotted" : ""}`}
|
||||
role="dialog"
|
||||
aria-label="Chat with Bestie"
|
||||
style={detached ? ({ "--bestie-x": `${position.x}px`, "--bestie-y": `${position.y}px` } as CSSProperties) : undefined}
|
||||
style={detached ? ({ "--bestie-x": `${position.x}px`, "--bestie-y": `${position.y}px`, ...(slotSize ? { "--bestie-slot-width": `${slotSize.width}px`, "--bestie-slot-height": `${slotSize.height}px` } : {}) } as CSSProperties) : undefined}
|
||||
>
|
||||
<header
|
||||
className="bestie-chat-header"
|
||||
@@ -1225,6 +1259,7 @@ function BestieChat({ onClose }: { onClose: () => void }) {
|
||||
onPointerMove={updateDrag}
|
||||
onPointerUp={endDrag}
|
||||
onPointerCancel={endDrag}
|
||||
onLostPointerCapture={endDrag}
|
||||
>
|
||||
<div><img src="/snek.png" alt="" /><strong>Bestie</strong></div>
|
||||
<button type="button" aria-label="Close Bestie chat" onClick={closeChat}><X aria-hidden="true" size={18} strokeWidth={1.7} /></button>
|
||||
|
||||
@@ -82,8 +82,10 @@ body { background: #e6f2f5; color: var(--ink); font-family: Inter, ui-sans-serif
|
||||
animation: none;
|
||||
will-change: transform;
|
||||
}
|
||||
.bestie-chat.is-detached { z-index: 10; background: rgba(255,255,255,.9); transition: transform 240ms cubic-bezier(.455,.03,.515,.955), box-shadow 180ms ease; }
|
||||
.bestie-chat.is-detached { z-index: 10; border: 1px solid rgba(255,255,255,.96); border-radius: 28px; background: rgba(255,255,255,.94); box-shadow: 0 18px 44px rgba(53,76,86,.16), inset 0 1px 0 rgba(255,255,255,.9); transition: transform 240ms cubic-bezier(.455,.03,.515,.955), width 240ms cubic-bezier(.455,.03,.515,.955), height 240ms cubic-bezier(.455,.03,.515,.955), border-radius 240ms cubic-bezier(.455,.03,.515,.955), box-shadow 180ms ease; }
|
||||
.bestie-chat.is-detached.is-dragging { z-index: 45; }
|
||||
.bestie-chat.is-detached.is-connected { box-shadow: none; }
|
||||
.bestie-chat.is-slotted { width: var(--bestie-slot-width); height: var(--bestie-slot-height); }
|
||||
.bestie-chat.is-dragging { transition: none; }
|
||||
.bestie-chat.is-tearing { transform: translate3d(calc(var(--bestie-x) + var(--messages-pan-x)), calc(var(--bestie-y) + var(--messages-pan-y)), 0) scale(1.018); box-shadow: 0 24px 64px rgba(53,76,86,.22), inset 0 1px 0 rgba(255,255,255,.9); }
|
||||
.bestie-chat.is-merged-sticky { transform: translate3d(var(--bestie-x), var(--bestie-y), 0) scale(1); }
|
||||
@@ -314,6 +316,11 @@ body { background: #e6f2f5; color: var(--ink); font-family: Inter, ui-sans-serif
|
||||
.bestie-merge-target-right .message-panel { border-top-right-radius: 0; border-bottom-right-radius: 0; }
|
||||
.bestie-merge-target-top .message-panel { border-top-left-radius: 0; border-top-right-radius: 0; }
|
||||
.bestie-merge-target-bottom .message-panel { border-bottom-right-radius: 0; border-bottom-left-radius: 0; }
|
||||
.bestie-is-slotted.bestie-merge-target-left .message-panel { width: calc(100% - var(--bestie-slot-width)); margin-left: var(--bestie-slot-width); }
|
||||
.bestie-is-slotted.bestie-merge-target-right .message-panel { width: calc(100% - var(--bestie-slot-width)); }
|
||||
.bestie-is-slotted.bestie-merge-target-top .message-panel { height: calc(100% - var(--bestie-slot-height)); margin-top: var(--bestie-slot-height); }
|
||||
.bestie-is-slotted.bestie-merge-target-bottom .message-panel { height: calc(100% - var(--bestie-slot-height)); }
|
||||
.bestie-is-slotted .message-panel { transition: transform 240ms cubic-bezier(.455,.03,.515,.955), width 240ms cubic-bezier(.455,.03,.515,.955), height 240ms cubic-bezier(.455,.03,.515,.955), margin 240ms cubic-bezier(.455,.03,.515,.955), border-radius 240ms cubic-bezier(.455,.03,.515,.955), box-shadow 180ms ease; }
|
||||
.panel-drag-handle { cursor: grab; touch-action: none; user-select: none; }
|
||||
.message-panel-cell.is-pulling .panel-drag-handle { cursor: grabbing; }
|
||||
.channel-panel { padding: 10px; }
|
||||
|
||||
Reference in New Issue
Block a user