).detail)
+ window.addEventListener(RAIL_TIP, on)
+ return () => window.removeEventListener(RAIL_TIP, on)
+ }, [])
+ if (!tip?.rect) return null
+ return createPortal(
+
+ {tip.text}
+
,
+ document.body
+ )
+}
diff --git a/src/renderer/src/components/TitleBar.tsx b/src/renderer/src/components/TitleBar.tsx
index 2782752..8f75b96 100644
--- a/src/renderer/src/components/TitleBar.tsx
+++ b/src/renderer/src/components/TitleBar.tsx
@@ -61,11 +61,11 @@ export function TitleBar({ title }: { title?: string } = {}): JSX.Element {
void window.moho.window.minimize()} />
void window.moho.window.toggleMaximize().then(setMaximized)}
/>
- void window.moho.window.close()} />
+ void window.moho.window.close()} />
)
diff --git a/src/renderer/src/lib/railtip.ts b/src/renderer/src/lib/railtip.ts
new file mode 100644
index 0000000..64f1029
--- /dev/null
+++ b/src/renderer/src/lib/railtip.ts
@@ -0,0 +1,24 @@
+/**
+ * The rail's name flyout, instead of the browser's own tooltip.
+ *
+ * A native `title` waits most of a second before it appears and cannot be
+ * styled; the rail is where somebody moves the pointer down a column of
+ * unlabelled squares asking which is which, so the answer has to be there as
+ * the pointer arrives. Tiles say what to show; one flyout draws it.
+ */
+export const RAIL_TIP = 'moho:rail-tip'
+
+export interface RailTipDetail {
+ text: string
+ /** Where the tile is, in window coordinates, or null to hide the flyout. */
+ rect: { top: number; right: number; height: number } | null
+}
+
+export function showRailTip(el: HTMLElement, text: string): void {
+ const r = el.getBoundingClientRect()
+ window.dispatchEvent(new CustomEvent(RAIL_TIP, { detail: { text, rect: { top: r.top, right: r.right, height: r.height } } }))
+}
+
+export function hideRailTip(): void {
+ window.dispatchEvent(new CustomEvent(RAIL_TIP, { detail: { text: '', rect: null } }))
+}