Skip to content

Commit 687df7f

Browse files
authored
Added landing page videos (#43)
2 parents 4cf927c + a36ccde commit 687df7f

13 files changed

Lines changed: 85 additions & 95 deletions

lib/src/components/HeaderActionButton.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { PopupButtonRow, renderShortcuts } from './design';
55
export interface HeaderActionButtonProps {
66
className: string;
77
ariaLabel: string;
8-
tooltip?: string;
8+
tooltip?: string | null;
99
tooltipDetail?: string;
1010
tooltipAlign?: 'left' | 'right';
1111
onMouseDownCapture?: (e: React.MouseEvent<HTMLButtonElement>) => void;
@@ -32,7 +32,7 @@ export function HeaderActionButton({
3232
const buttonRef = useRef<HTMLButtonElement>(null);
3333
const [isVisible, setIsVisible] = useState(false);
3434
const [tooltipStyle, setTooltipStyle] = useState<React.CSSProperties | null>(null);
35-
const tooltipPrimary = tooltip ?? ariaLabel;
35+
const tooltipPrimary = tooltip === null ? null : (tooltip ?? ariaLabel);
3636

3737
useEffect(() => {
3838
if (!isVisible || !buttonRef.current) return;
@@ -73,6 +73,7 @@ export function HeaderActionButton({
7373
}}
7474
onClick={(e) => {
7575
e.stopPropagation();
76+
setIsVisible(false);
7677
onClick(e);
7778
}}
7879
onContextMenu={onContextMenu ? (e) => {
@@ -89,7 +90,7 @@ export function HeaderActionButton({
8990
{children}
9091
</button>
9192
</div>
92-
{isVisible && tooltipStyle && createPortal(
93+
{isVisible && tooltipStyle && tooltipPrimary && createPortal(
9394
<PopupButtonRow
9495
role="tooltip"
9596
className="pointer-events-none z-[9999] whitespace-nowrap px-2 py-1.5"

lib/src/components/TerminalPane.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from '../lib/terminal-registry';
1010
import { SelectionOverlay } from './SelectionOverlay';
1111
import { SelectionPopup } from './SelectionPopup';
12+
import { MouseOverrideBanner } from './wall/MouseOverrideBanner';
1213
import { TERMINAL_BOTTOM_RADIUS_CLASS } from './design';
1314

1415
interface TerminalPaneProps {
@@ -54,6 +55,7 @@ export function TerminalPane({ id, isFocused = true }: TerminalPaneProps) {
5455
<div ref={containerRef} className={`relative h-full w-full overflow-hidden bg-terminal-bg ${TERMINAL_BOTTOM_RADIUS_CLASS}`}>
5556
<SelectionOverlay terminalId={id} />
5657
<SelectionPopup terminalId={id} />
58+
<MouseOverrideBanner terminalId={id} />
5759
</div>
5860
);
5961
}
Lines changed: 21 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,46 @@
1-
import { useEffect, useLayoutEffect, useState } from 'react';
2-
import { createPortal } from 'react-dom';
1+
import { useEffect, useState, useSyncExternalStore } from 'react';
32
import { PopupButtonRow, popupButton } from '../design';
4-
import { clampOverlayPosition } from '../../lib/ui-geometry';
3+
import {
4+
DEFAULT_MOUSE_SELECTION_STATE,
5+
getMouseSelectionSnapshot,
6+
setOverride as setMouseOverride,
7+
subscribeToMouseSelection,
8+
} from '../../lib/mouse-selection';
59

6-
export function MouseOverrideBanner({
7-
anchor,
8-
onMakePermanent,
9-
onCancel,
10-
}: {
11-
anchor: HTMLElement;
12-
onMakePermanent: () => void;
13-
onCancel: () => void;
14-
}) {
15-
const [pos, setPos] = useState<{ x: number; y: number } | null>(null);
10+
export function MouseOverrideBanner({ terminalId }: { terminalId: string }) {
11+
const states = useSyncExternalStore(subscribeToMouseSelection, getMouseSelectionSnapshot);
12+
const state = states.get(terminalId) ?? DEFAULT_MOUSE_SELECTION_STATE;
13+
const visible = state.override === 'temporary';
1614
const [flashed, setFlashed] = useState<'sticky' | 'cancel' | null>(null);
1715

18-
useLayoutEffect(() => {
19-
const update = () => {
20-
const r = anchor.getBoundingClientRect();
21-
setPos({ x: r.left, y: r.bottom + 4 });
22-
};
23-
update();
24-
window.addEventListener('scroll', update, true);
25-
window.addEventListener('resize', update);
26-
return () => {
27-
window.removeEventListener('scroll', update, true);
28-
window.removeEventListener('resize', update);
29-
};
30-
}, [anchor]);
31-
3216
useEffect(() => {
3317
if (!flashed) return;
3418
const id = window.setTimeout(() => {
35-
if (flashed === 'sticky') onMakePermanent();
36-
else onCancel();
19+
setMouseOverride(terminalId, flashed === 'sticky' ? 'permanent' : 'off');
20+
setFlashed(null);
3721
}, 260);
3822
return () => window.clearTimeout(id);
39-
}, [flashed, onMakePermanent, onCancel]);
23+
}, [flashed, terminalId]);
4024

41-
if (!pos) return null;
25+
if (!visible) return null;
4226

43-
return createPortal(
27+
return (
4428
<PopupButtonRow
45-
className="z-[9999]"
46-
style={clampOverlayPosition({ left: pos.x, top: pos.y, width: 340, height: 32 })}
29+
className="absolute right-1 top-1 z-20 whitespace-nowrap"
4730
onMouseDown={(e) => e.stopPropagation()}
4831
role="status"
4932
>
50-
<span className="px-1.5 py-0.5">Temporary mouse override until mouse-up.</span>
33+
<span className="px-1.5 py-0.5 text-muted">Temporary mouse override until mouse-up.</span>
5134
<button
5235
type="button"
53-
className={popupButton({ tone: 'muted', flashed: flashed === 'sticky' })}
36+
className={popupButton({ flashed: flashed === 'sticky' })}
5437
onClick={() => !flashed && setFlashed('sticky')}
5538
>Make sticky</button>
5639
<button
5740
type="button"
58-
className={popupButton({ tone: 'muted', flashed: flashed === 'cancel' })}
41+
className={popupButton({ flashed: flashed === 'cancel' })}
5942
onClick={() => !flashed && setFlashed('cancel')}
6043
>Cancel</button>
61-
</PopupButtonRow>,
62-
document.body,
44+
</PopupButtonRow>
6345
);
6446
}
65-

lib/src/components/wall/TerminalPaneHeader.tsx

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ import {
4040
WindowFocusedContext,
4141
ZoomedContext,
4242
} from './wall-context';
43-
import { MouseOverrideBanner } from './MouseOverrideBanner';
4443

4544
const tabVariant = tv({
4645
base: `flex h-full w-full cursor-grab items-center gap-1.5 ${TERMINAL_TOP_RADIUS_CLASS} pl-2 pr-[5px] text-sm leading-none font-mono select-none active:cursor-grabbing`,
@@ -68,15 +67,16 @@ export function TerminalPaneHeader({ api }: IDockviewPanelHeaderProps) {
6867
const mouseState = mouseStates.get(api.id) ?? DEFAULT_MOUSE_SELECTION_STATE;
6968
const showMouseIcon = mouseState.mouseReporting !== 'none';
7069
const inOverride = mouseState.override !== 'off';
71-
const mouseIconTooltip = inOverride
70+
const mouseIconTooltip: string | null = mouseState.override === 'permanent'
7271
? "You're overriding the TUI's mouse capture. Click to restore."
73-
: 'TUI is intercepting mouse commands. Click to override.';
72+
: mouseState.override === 'temporary'
73+
? null
74+
: 'TUI is intercepting mouse commands. Click to override.';
7475
const mouseIconAriaLabel = inOverride ? 'Restore mouse capture' : 'Override mouse capture';
7576
const isSelected = selectedId === api.id;
7677
const isActiveHeader = mode === 'passthrough' && isSelected && windowFocused;
7778
const isRenaming = renamingId === api.id;
7879
const tabRef = useRef<HTMLDivElement>(null);
79-
const [mouseIconAnchor, setMouseIconAnchor] = useState<HTMLDivElement | null>(null);
8080
const suppressAlertClickRef = useRef(false);
8181
const [tier, setTier] = useState<HeaderTier>('full');
8282
const [dialogTriggerRect, setDialogTriggerRect] = useState<DOMRect | null>(null);
@@ -210,7 +210,7 @@ export function TerminalPaneHeader({ api }: IDockviewPanelHeaderProps) {
210210
{!isRenaming && (
211211
<>
212212
{showMouseIcon && (
213-
<div ref={setMouseIconAnchor} className="ml-1 shrink-0">
213+
<div className="ml-1 shrink-0">
214214
<HeaderActionButton
215215
className="flex h-5 min-w-5 items-center justify-center rounded transition-colors shrink-0 hover:bg-current/10"
216216
onMouseDown={(e) => e.stopPropagation()}
@@ -231,13 +231,6 @@ export function TerminalPaneHeader({ api }: IDockviewPanelHeaderProps) {
231231
</HeaderActionButton>
232232
</div>
233233
)}
234-
{mouseIconAnchor && mouseState.override === 'temporary' && (
235-
<MouseOverrideBanner
236-
anchor={mouseIconAnchor}
237-
onMakePermanent={() => setMouseOverride(api.id, 'permanent')}
238-
onCancel={() => setMouseOverride(api.id, 'off')}
239-
/>
240-
)}
241234
{tier === 'full' && (
242235
<div className="ml-1 flex shrink-0 items-center gap-0.5">
243236
<HeaderActionButton

lib/src/stories/MouseHeaderIcon.stories.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
type WallMode,
1010
type WallActions,
1111
} from '../components/Wall';
12+
import { MouseOverrideBanner } from '../components/wall/MouseOverrideBanner';
1213
import {
1314
setMouseReporting,
1415
setOverride,
@@ -69,6 +70,9 @@ function MouseIconStoryFrame({
6970
tabLocation={'header' as Parameters<typeof TerminalPaneHeader>[0]['tabLocation']}
7071
/>
7172
</div>
73+
<div className="relative" style={{ height: 40 }}>
74+
<MouseOverrideBanner terminalId={SESSION_ID} />
75+
</div>
7276
</div>
7377
</RenamingIdContext.Provider>
7478
</WallActionsContext.Provider>
@@ -99,7 +103,7 @@ export const ReportingOn: Story = {
99103
};
100104

101105
export const TemporaryOverride: Story = {
102-
args: { mouseReporting: 'vt200', override: 'temporary' },
106+
args: { mouseReporting: 'vt200', override: 'temporary', width: 500 },
103107
};
104108

105109
export const PermanentOverride: Story = {
-464 KB
Binary file not shown.
-122 KB
Binary file not shown.
-6.03 KB
Binary file not shown.

website/src/assets/video-alert.mp4

12.2 MB
Binary file not shown.

0 commit comments

Comments
 (0)