mirror of
https://github.com/block/buzz.git
synced 2026-10-29 13:31:44 +00:00
fix(desktop): refine panel split physics
Signed-off-by: cynfria <yescynthia@gmail.com>
This commit is contained in:
@@ -1009,6 +1009,8 @@ function MessagesView() {
|
||||
cell: HTMLElement;
|
||||
x: number;
|
||||
y: number;
|
||||
detachedAtStart: boolean;
|
||||
hasBroken: boolean;
|
||||
} | null>(null);
|
||||
|
||||
const beginPanelDrag = (
|
||||
@@ -1030,6 +1032,8 @@ function MessagesView() {
|
||||
cell,
|
||||
x: current.x,
|
||||
y: current.y,
|
||||
detachedAtStart: current.detached,
|
||||
hasBroken: current.detached,
|
||||
};
|
||||
cell.classList.add("is-pulling", "is-active-drag");
|
||||
};
|
||||
@@ -1041,15 +1045,20 @@ function MessagesView() {
|
||||
endPanelDrag(event);
|
||||
return;
|
||||
}
|
||||
drag.x = Math.max(-340, Math.min(340, drag.originX + event.clientX - drag.startX));
|
||||
drag.y = Math.max(-190, Math.min(190, drag.originY + event.clientY - drag.startY));
|
||||
const rawX = Math.max(-340, Math.min(340, drag.originX + event.clientX - drag.startX));
|
||||
const rawY = Math.max(-190, Math.min(190, drag.originY + event.clientY - drag.startY));
|
||||
const rawDistance = Math.hypot(rawX, rawY);
|
||||
if (!drag.detachedAtStart && rawDistance > 112) drag.hasBroken = true;
|
||||
const resistance = drag.detachedAtStart || drag.hasBroken
|
||||
? 1
|
||||
: 0.76 + Math.min(rawDistance / 112, 1) * 0.12;
|
||||
drag.x = rawX * resistance;
|
||||
drag.y = rawY * resistance;
|
||||
const distance = Math.hypot(drag.x, drag.y);
|
||||
drag.cell.style.setProperty("--panel-x", `${drag.x}px`);
|
||||
drag.cell.style.setProperty("--panel-y", `${drag.y}px`);
|
||||
drag.cell.style.setProperty("--tether-distance", `${Math.min(distance, 126)}px`);
|
||||
drag.cell.style.setProperty("--tether-angle", `${Math.atan2(drag.y, drag.x)}rad`);
|
||||
drag.cell.style.setProperty("--tether-squeeze", String(Math.max(0.28, 1 - distance / 170)));
|
||||
drag.cell.classList.toggle("is-breaking", distance > 94);
|
||||
drag.cell.style.setProperty("--panel-tilt", `${Math.max(-1.2, Math.min(1.2, (event.clientX - drag.startX) * 0.012))}deg`);
|
||||
drag.cell.classList.toggle("is-breaking", drag.hasBroken);
|
||||
};
|
||||
|
||||
const endPanelDrag = (event: ReactPointerEvent<HTMLElement>) => {
|
||||
@@ -1059,12 +1068,13 @@ function MessagesView() {
|
||||
event.currentTarget.releasePointerCapture(event.pointerId);
|
||||
}
|
||||
const distance = Math.hypot(drag.x, drag.y);
|
||||
const shouldAttach = distance < (layouts[drag.id].detached ? 54 : 72);
|
||||
const shouldAttach = drag.detachedAtStart ? distance < 54 : !drag.hasBroken;
|
||||
const next = shouldAttach
|
||||
? { x: 0, y: 0, detached: false }
|
||||
: { x: drag.x, y: drag.y, detached: true };
|
||||
drag.cell.style.setProperty("--panel-x", `${next.x}px`);
|
||||
drag.cell.style.setProperty("--panel-y", `${next.y}px`);
|
||||
drag.cell.style.setProperty("--panel-tilt", "0deg");
|
||||
drag.cell.classList.remove("is-pulling", "is-breaking", "is-active-drag");
|
||||
setLayouts((current) => ({ ...current, [drag.id]: next }));
|
||||
dragRef.current = null;
|
||||
@@ -1073,6 +1083,7 @@ function MessagesView() {
|
||||
const panelStyle = (id: MessagePanelId) => ({
|
||||
"--panel-x": `${layouts[id].x}px`,
|
||||
"--panel-y": `${layouts[id].y}px`,
|
||||
"--panel-tilt": "0deg",
|
||||
}) as CSSProperties;
|
||||
|
||||
const dragHandlers = (id: MessagePanelId) => ({
|
||||
@@ -1084,9 +1095,8 @@ function MessagesView() {
|
||||
|
||||
return (
|
||||
<section className="messages-stage" aria-label="Composable messages workspace">
|
||||
<div className={`messages-board${Object.values(layouts).some((panel) => panel.detached) ? " has-detached-panel" : ""}`}>
|
||||
<div className={`messages-board${Object.values(layouts).some((panel) => panel.detached) ? " has-detached-panel" : ""}${layouts.sidebar.detached ? " sidebar-is-detached" : ""}${layouts.chat.detached ? " chat-is-detached" : ""}${layouts.thread.detached ? " thread-is-detached" : ""}`}>
|
||||
<div className={`message-panel-cell sidebar-cell${layouts.sidebar.detached ? " is-detached" : ""}`} style={panelStyle("sidebar")}>
|
||||
<div className="liquid-tether" aria-hidden="true" />
|
||||
<aside className="message-panel channel-panel">
|
||||
<div className="panel-drag-handle channel-search-wrap" {...dragHandlers("sidebar")}>
|
||||
<Search aria-hidden="true" size={18} strokeWidth={1.7} />
|
||||
@@ -1104,7 +1114,6 @@ function MessagesView() {
|
||||
</div>
|
||||
|
||||
<div className={`message-panel-cell chat-cell${layouts.chat.detached ? " is-detached" : ""}`} style={panelStyle("chat")}>
|
||||
<div className="liquid-tether" aria-hidden="true" />
|
||||
<article className="message-panel chat-panel">
|
||||
<header className="panel-drag-handle chat-header" {...dragHandlers("chat")}>
|
||||
<div><LockKeyhole aria-hidden="true" size={18} strokeWidth={1.7} /><strong>buzz-interface-squad</strong></div>
|
||||
@@ -1120,7 +1129,6 @@ function MessagesView() {
|
||||
</div>
|
||||
|
||||
<div className={`message-panel-cell thread-cell${layouts.thread.detached ? " is-detached" : ""}`} style={panelStyle("thread")}>
|
||||
<div className="liquid-tether" aria-hidden="true" />
|
||||
<aside className="message-panel thread-panel">
|
||||
<header className="panel-drag-handle thread-header" {...dragHandlers("thread")}><div><PanelRightOpen aria-hidden="true" size={18} strokeWidth={1.7} /><strong>Thread</strong></div><button type="button" aria-label="Close thread"><X aria-hidden="true" size={18} strokeWidth={1.7} /></button></header>
|
||||
<div className="thread-content">
|
||||
|
||||
@@ -161,9 +161,8 @@ body { background: #e6f2f5; color: var(--ink); font-family: Inter, ui-sans-serif
|
||||
.message-panel-cell {
|
||||
--panel-x: 0px;
|
||||
--panel-y: 0px;
|
||||
--tether-distance: 0px;
|
||||
--tether-angle: 0rad;
|
||||
--tether-squeeze: 1;
|
||||
--panel-tilt: 0deg;
|
||||
--panel-scale: 1;
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
@@ -180,7 +179,7 @@ body { background: #e6f2f5; color: var(--ink); font-family: Inter, ui-sans-serif
|
||||
overflow: hidden;
|
||||
background: rgba(255,255,255,.94);
|
||||
border: 1px solid rgba(255,255,255,.96);
|
||||
transform: translate3d(var(--panel-x), var(--panel-y), 0);
|
||||
transform: translate3d(var(--panel-x), var(--panel-y), 0) rotate(var(--panel-tilt)) scale(var(--panel-scale));
|
||||
transition:
|
||||
transform 480ms cubic-bezier(.22,1,.36,1),
|
||||
border-radius 260ms ease-out,
|
||||
@@ -190,33 +189,21 @@ body { background: #e6f2f5; color: var(--ink); font-family: Inter, ui-sans-serif
|
||||
.sidebar-cell .message-panel { border-radius: 28px 0 0 28px; }
|
||||
.chat-cell .message-panel { border-radius: 0; border-left-color: rgba(38,38,38,.09); }
|
||||
.thread-cell .message-panel { border-radius: 0 28px 28px 0; border-left-color: rgba(38,38,38,.09); }
|
||||
.messages-board.sidebar-is-detached .chat-cell:not(.is-detached) .message-panel { border-radius: 28px 0 0 28px; }
|
||||
.messages-board.thread-is-detached .chat-cell:not(.is-detached) .message-panel { border-radius: 0 28px 28px 0; }
|
||||
.messages-board.sidebar-is-detached.thread-is-detached .chat-cell:not(.is-detached) .message-panel,
|
||||
.messages-board.chat-is-detached .sidebar-cell:not(.is-detached) .message-panel,
|
||||
.messages-board.chat-is-detached .thread-cell:not(.is-detached) .message-panel { border-radius: 28px; }
|
||||
.message-panel-cell.is-detached .message-panel,
|
||||
.message-panel-cell.is-breaking .message-panel {
|
||||
border-radius: 28px;
|
||||
box-shadow: 0 18px 44px rgba(53,76,86,.16), inset 0 1px 0 rgba(255,255,255,.9);
|
||||
}
|
||||
.message-panel-cell.is-pulling { --panel-scale: .997; }
|
||||
.message-panel-cell.is-pulling.is-breaking { --panel-scale: 1; }
|
||||
.message-panel-cell.is-pulling .message-panel { transition: border-radius 180ms ease-out, box-shadow 180ms ease-out; }
|
||||
.panel-drag-handle { cursor: grab; touch-action: none; user-select: none; }
|
||||
.message-panel-cell.is-pulling .panel-drag-handle { cursor: grabbing; }
|
||||
.liquid-tether {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: calc(var(--tether-distance) + 88px);
|
||||
height: 112px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255,255,255,.92);
|
||||
opacity: 0;
|
||||
transform-origin: 0 50%;
|
||||
transform: translateY(-50%) rotate(var(--tether-angle)) scaleY(var(--tether-squeeze));
|
||||
transition: opacity 90ms ease, transform 120ms ease-out;
|
||||
pointer-events: none;
|
||||
filter: blur(1px);
|
||||
}
|
||||
.message-panel-cell.is-pulling .liquid-tether { opacity: 1; transition: none; }
|
||||
.message-panel-cell.is-pulling.is-breaking .liquid-tether { opacity: 0; transform: translateY(-50%) rotate(var(--tether-angle)) scaleY(.08); transition: opacity 100ms ease-out, transform 160ms ease-out; }
|
||||
|
||||
.channel-panel { padding: 10px; }
|
||||
.channel-search-wrap { display: flex; height: 42px; align-items: center; gap: 10px; padding: 0 12px; border-radius: 13px; background: rgba(243,243,243,.8); }
|
||||
.channel-search-wrap svg { flex: 0 0 auto; color: rgba(38,38,38,.5); }
|
||||
@@ -453,7 +440,7 @@ body { background: #e6f2f5; color: var(--ink); font-family: Inter, ui-sans-serif
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.wrapped-labels { will-change: auto; }
|
||||
.orbit-system, .task-detail-panel { transition-duration: 1ms; animation-duration: 1ms; }
|
||||
.message-panel, .liquid-tether { transition: none; }
|
||||
.message-panel { transition: none; }
|
||||
.response-composer { transition: none; }
|
||||
.response-composer textarea { animation: none; }
|
||||
.project-name-pill { transition: none; }
|
||||
|
||||
Reference in New Issue
Block a user