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.
27 lines
902 B
TypeScript
27 lines
902 B
TypeScript
import type { ReactNode } from "react";
|
|
import type { LucideIcon } from "lucide-react";
|
|
import { AlertTriangle } from "lucide-react";
|
|
|
|
// An in-flow warning that sits INSIDE a panel, next to the thing it is about — as opposed to
|
|
// Banner, which is the full-bleed top-of-app bar (border-b, app chrome) and looks wrong nested in a
|
|
// card. Same warn tokens as Banner's warning tone so the two read as one system.
|
|
export default function InlineNotice({
|
|
children,
|
|
icon: Icon = AlertTriangle,
|
|
className = "",
|
|
}: {
|
|
children: ReactNode;
|
|
icon?: LucideIcon;
|
|
className?: string;
|
|
}) {
|
|
return (
|
|
<div
|
|
role="status"
|
|
className={`flex items-start gap-2 rounded-xl border border-warn/30 bg-warn/15 px-3 py-2 text-sm text-fg ${className}`}
|
|
>
|
|
<Icon className="w-4 h-4 shrink-0 mt-0.5 text-warn" aria-hidden />
|
|
<span className="min-w-0">{children}</span>
|
|
</div>
|
|
);
|
|
}
|