Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions packages/chrome/src/components/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export function Menu(
closing: boolean;
x: number;
y: number;
transformOriginX: string;
transformOriginY: string;
}
>
) {
Expand All @@ -35,6 +37,8 @@ export function Menu(
});
this.x = 0;
this.y = 0;
this.transformOriginX = "left";
this.transformOriginY = "top";

const [lock, unlock] = requestUnfocusFrames();

Expand Down Expand Up @@ -84,8 +88,14 @@ export function Menu(

const maxX = docWidth - width - padding;
const maxY = docHeight - height - padding;
if (this.x > maxX) this.x = maxX;
if (this.y > maxY) this.y = maxY;
if (this.x > maxX) {
this.x = maxX;
this.transformOriginX = "right";
}
if (this.y > maxY) {
this.y = maxY;
this.transformOriginY = "bottom";
}
if (this.x < padding) this.x = padding;
if (this.y < padding) this.y = padding;

Expand All @@ -100,7 +110,7 @@ export function Menu(
};
return (
<div
style={use`--x: ${this.x}px; --y: ${this.y}px;`}
style={use`--x: ${this.x}px; --y: ${this.y}px; --transform-origin-x: ${this.transformOriginX}; --transform-origin-y: ${this.transformOriginY};`}
class:closing={use(this.closing)}
>
{this.items
Expand Down Expand Up @@ -163,16 +173,17 @@ Menu.style = css`
overflow: hidden;

transition:
opacity 0.15s ease,
transform 0.15s ease;
opacity 0.1s ease,
transform 0.12s cubic-bezier(0.35, 0.15, 0, 1.8);
opacity: 1;
transform: scale(100%);
transform: scaleX(100%) scaleY(100%);
transform-origin: var(--transform-origin-x) var(--transform-origin-y);
}
.separator {
border-top: 1px solid var(--text-20);
}
:scope.closing {
transform: scale(95%);
transform: scaleX(95%) scaleY(87%);
opacity: 0;
}
.item {
Expand Down
5 changes: 4 additions & 1 deletion packages/chrome/src/components/TabStrip/DragTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export function DragTab(
},
]);

// Open-tab animation: expands the tab container from width 0 to full computed width.
this.root.animate(
[
{
Expand All @@ -67,7 +68,8 @@ export function DragTab(
{},
],
{
duration: 100,
duration: 200,
easing: "cubic-bezier(.25,.5,0,1.15)",
fill: "forwards",
}
);
Expand All @@ -81,6 +83,7 @@ export function DragTab(
class="tab"
data-id={this.id}
on:transitionend={() => {
// Clears programmatically assigned move transition/z-index after tab translate animation ends.
this.root.style.transition = "";
this.root.style.zIndex = "0";
this.transitionend();
Expand Down
58 changes: 48 additions & 10 deletions packages/chrome/src/components/TabStrip/TabStrip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type VisualTab = {
dragoffset: number;
dragpos: number;
startdragpos: number;
closing: boolean;

width: number;
pos: number;
Expand Down Expand Up @@ -43,7 +44,10 @@ export function TabStrip(

const TAB_PADDING = 6;
const TAB_MAX_SIZE = 231;
const TAB_TRANSITION = "250ms ease";
// Reorder/move animation for tabs and trailing controls in the strip.
const TAB_TRANSITION = "225ms cubic-bezier(.43,.52,0,1.15)";
const TAB_STAGGER_STEP = 18;
const TAB_STAGGER_MAX = 144;

let transitioningTabs = 0;

Expand Down Expand Up @@ -76,11 +80,15 @@ export function TabStrip(

const getTabWidth = () => {
let total = getRootWidth();
const visibleTabCount = this.visualtabs.filter(
(tab) => !tab.closing
).length;
const count = Math.max(visibleTabCount, 1);

// remove padding
total -= TAB_PADDING * (this.visualtabs.length - 1);
total -= TAB_PADDING * (count - 1);

const each = total / this.visualtabs.length;
const each = total / count;

return Math.min(TAB_MAX_SIZE, Math.floor(each));
};
Expand All @@ -105,24 +113,52 @@ export function TabStrip(

let dragpos = -1;
let currpos = getLayoutStart();
let staggerIndex = 0;
let movedTabs = 0;
for (const tab of this.visualtabs) {
if (tab.closing) {
// Closing tabs animate their own width; keep their current transform while
// siblings/new-tab button reflow into post-close slots.
const tabPos = tab.dragpos != -1 ? tab.dragpos : tab.pos;
tab.root.style.transform = `translateX(${tabPos}px)`;
tab.pos = tabPos;
continue;
}

tab.root.style.width = width + "px";

const tabPos = tab.dragpos != -1 ? tab.dragpos : currpos;
// Moves each tab horizontally to its computed slot.
tab.root.style.transform = `translateX(${tabPos}px)`;
if (transition && tab.dragpos == -1 && tab.pos != tabPos) {
tab.root.style.transition = `transform ${TAB_TRANSITION}`;
this.afterEl.style.transition = `transform ${TAB_TRANSITION}`;
const delay = Math.min(
staggerIndex * TAB_STAGGER_STEP,
TAB_STAGGER_MAX
);
// Animates tab movement when tabs are inserted/removed/reordered.
tab.root.style.transition = `transform ${TAB_TRANSITION} ${delay}ms`;
transitioningTabs++;
movedTabs++;
}
dragpos = Math.max(dragpos, tab.dragpos + width + TAB_PADDING);

tab.pos = tabPos;
tab.width = width;
currpos += width + TAB_PADDING;
staggerIndex++;
}

if (transition && movedTabs > 0) {
const afterDelay = Math.min(
staggerIndex * TAB_STAGGER_STEP,
TAB_STAGGER_MAX
);
// Animate trailing "after" area (new-tab button container) with stagger too.
this.afterEl.style.transition = `transform ${TAB_TRANSITION} ${afterDelay}ms`;
}

const afterpos = Math.max(dragpos, currpos);
// Moves the trailing control area to stay after the last tab.
this.afterEl.style.transform = `translateX(${afterpos}px)`;
};

Expand Down Expand Up @@ -187,12 +223,11 @@ export function TabStrip(
};

const transitionend = () => {
transitioningTabs--;
transitioningTabs = Math.max(transitioningTabs - 1, 0);
if (transitioningTabs == 0) {
this.afterEl.style.transition = "";
this.tabs = this.tabs;
}

this.afterEl.style.transition = "";
};

use(this.tabs).listen(() => {
Expand Down Expand Up @@ -222,6 +257,7 @@ export function TabStrip(
dragoffset: -1,
dragpos: -1,
startdragpos: -1,
closing: false,
width: 0,
pos: getLayoutStart() + index * (getTabWidth() + TAB_PADDING),
};
Expand All @@ -233,7 +269,9 @@ export function TabStrip(
for (let vtab of this.visualtabs) {
if (!newvisualtabs.includes(vtab)) {
let indexof = this.visualtabs.indexOf(vtab);
vtab.closing = true;
newvisualtabs.splice(indexof, 0, vtab);
// Close-tab animation: collapses tab width to 0 before removal from DOM list.
let anim = vtab.root.animate(
[
{},
Expand All @@ -242,7 +280,8 @@ export function TabStrip(
},
],
{
duration: 100,
duration: 150,
easing: "cubic-bezier(.29,.44,.3,.94)",
fill: "forwards",
}
);
Expand Down Expand Up @@ -302,7 +341,6 @@ TabStrip.style = css`
padding: var(--tab-padding) 12px;
height: calc(var(--tab-height) + calc(var(--tab-padding) * 2));
z-index: 2;

position: relative;
}

Expand Down
30 changes: 20 additions & 10 deletions packages/chrome/src/components/TabStrip/TabTooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ export function TabTooltip(
) {
let wasActive = this.active;

const duration = 150;
const duration = 125;
const curve = "cubic-bezier(.35,.15,0,1.5)";
const visible = {
opacity: "1",
transform: "scale(100%)",
transform: "scaleX(100%) scaleY(100%)",
};
const hidden = {
opacity: "0",
transform: "scale(95%)",
transform: "scaleX(95%) scaleY(87%)",
};

let isClosing = false;
Expand All @@ -46,33 +47,39 @@ export function TabTooltip(

if (this.animate) {
let shift = lastX - x;
// Instantly applies the hidden->visible visual state (opacity + scale) before slide alignment.
this.root.animate([hidden, visible], {
duration: 0,
fill: "forwards",
});
// Reposition animation between adjacent tab tooltips: translateX from previous tooltip X to current X.
this.root.animate(
[
{ transform: `translateX(${shift}px)` },
{ transform: "translateX(0px)" },
],
{
duration: 300,
easing: "ease-out",
duration: 150,
easing: "cubic-bezier(.45,.25,0,1.09)",
}
);
this.animate = false;
} else {
// Standard tooltip enter animation: fades in and scales from 95% -> 100%.
this.root.animate([hidden, visible], {
duration,
easing: curve,
fill: "forwards",
});
}
lastX = x;
} else if (!active && wasActive) {
wasActive = false;
isClosing = true;
// Tooltip exit animation: fades out and scales down from 100% -> 95%.
this.root.animate([visible, hidden], {
duration,
easing: curve,
fill: "forwards",
}).onfinish = () => {
isClosing = false;
Expand Down Expand Up @@ -109,25 +116,28 @@ TabTooltip.style = css`
background: var(--popup);
border: 1px solid var(--popup_border);
border-radius: var(--radius);
width: 17em;
width: 18em;
gap: 0.25em;
flex-direction: column;
opacity: 0;
border-radius: var(--radius);
}
.text {
padding: 0.5em;
padding: 0.75em 0.67em;
display: flex;
flex-direction: column;
gap: 0.1em;
gap: 0.25em;
}
.title {
overflow: hidden;
overflow: clip visible;
white-space: nowrap;
text-overflow: ellipsis;
font-size: 0.9em;
font-weight: 500;
}
.hostname {
font-size: 12px;
font-size: 0.7em;
color: var(--text-60);
}

.img {
Expand Down
Loading