refactor(e10): move the platform components into components/ui/ and enforce the boundary
The app HAS a platform layer -- AnchoredPopover (22 importers, with arrows/typeahead/Escape/anchor-flip), QueryState (21), Modal (17), PageShell, DataTable, Tabs, Tooltip and ~26 more -- but it sat flat among ~160 feature components with nothing marking it as one. Several of those files describe themselves as 'the one place' or 'canonical' and were still bypassed by hand-rolled markup, because a layer nobody can see is a layer nobody uses; three separate inventory passes of this codebase misread it the same way. Moves the 33 domain-free modules into components/ui/, rewrites the imports in 89 files, and makes the boundary a lint rule rather than a convention: inside ui/ a one-level-up import (a feature component) and any lib/api slice are errors. The rule was verified to fire on both and to leave generic lib/ helpers alone. README.md states which side of the line a component belongs on and why. No behaviour change: 3x tsc, eslint, knip, prettier and 687 vitest tests all green; no backend file touched.
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
import { useState, useSyncExternalStore } from "react";
|
||||
import { PopoverSurface, useAnchoredPopover } from "./AnchoredPopover";
|
||||
import { hintsEnabled, subscribeHints } from "../../lib/hints";
|
||||
|
||||
type Side = "top" | "bottom" | "left" | "right";
|
||||
|
||||
/** Wrap any element to show a short glass hint caption on hover — by default only while the
|
||||
* app-wide hints toggle (Settings → Appearance) is on. Anchored by the R8 popover platform, so it
|
||||
* is never clipped by an overflow/stacking ancestor, flips when the preferred side runs out of room
|
||||
* and slides back inside the viewport near an edge.
|
||||
* `always` bypasses the hints toggle (for a label that IS the primary affordance, e.g. the slim nav
|
||||
* rail's flyout names); `openDelay` waits before showing (a rest-on-the-icon feel). */
|
||||
export default function Tooltip({
|
||||
hint,
|
||||
side = "top",
|
||||
always = false,
|
||||
openDelay = 0,
|
||||
children,
|
||||
}: {
|
||||
hint: string;
|
||||
side?: Side;
|
||||
always?: boolean;
|
||||
openDelay?: number;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const hintsOn = useSyncExternalStore(subscribeHints, hintsEnabled, hintsEnabled);
|
||||
const enabled = always || hintsOn;
|
||||
const [open, setOpen] = useState(false);
|
||||
const pop = useAnchoredPopover({
|
||||
open: open && enabled && !!hint,
|
||||
onOpenChange: setOpen,
|
||||
mode: "hover",
|
||||
placement: side,
|
||||
offset: 8,
|
||||
// Show/hide on contact by default (as the pre-platform tooltip did); `openDelay` adds a
|
||||
// rest-before-show for labels that shouldn't flash on a fast sweep across an icon row.
|
||||
hoverDelay: { open: openDelay, close: 0 },
|
||||
});
|
||||
|
||||
if (!enabled || !hint) return <>{children}</>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<span ref={pop.refs.setReference} {...pop.getReferenceProps()} className="inline-flex">
|
||||
{children}
|
||||
</span>
|
||||
{/* Passive caption: it must not swallow a click aimed at a neighbouring control that it
|
||||
overlaps while the hover-close delay runs out. */}
|
||||
<PopoverSurface
|
||||
popover={pop}
|
||||
passthrough
|
||||
className="glass w-max max-w-[240px] px-2.5 py-1.5 rounded-lg text-xs leading-snug text-fg font-normal normal-case tracking-normal animate-[fadeIn_0.12s_ease]"
|
||||
>
|
||||
{hint}
|
||||
</PopoverSurface>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user