feat(ui): expose grouped component entrypoints

This commit is contained in:
vince 2026-06-20 07:31:35 +02:00
parent cb446e2a6e
commit 6a0c57e93c
7 changed files with 105 additions and 0 deletions

View file

@ -10,6 +10,26 @@
"development": "./src/index.ts", "development": "./src/index.ts",
"default": "./dist/index.js" "default": "./dist/index.js"
}, },
"./foundation": {
"types": "./dist/components/foundation/index.d.ts",
"development": "./src/components/foundation/index.ts",
"default": "./dist/components/foundation/index.js"
},
"./frames": {
"types": "./dist/components/frames/index.d.ts",
"development": "./src/components/frames/index.ts",
"default": "./dist/components/frames/index.js"
},
"./operations": {
"types": "./dist/components/operations/index.d.ts",
"development": "./src/components/operations/index.ts",
"default": "./dist/components/operations/index.js"
},
"./telemetry": {
"types": "./dist/components/telemetry/index.d.ts",
"development": "./src/components/telemetry/index.ts",
"default": "./dist/components/telemetry/index.js"
},
"./styles.css": { "./styles.css": {
"development": "./src/styles.css", "development": "./src/styles.css",
"default": "./dist/styles.css" "default": "./dist/styles.css"

View file

@ -0,0 +1,8 @@
export { Badge } from "./Badge";
export { Button } from "./Button";
export { IconButton } from "./IconButton";
export { IconGlyph } from "./IconGlyph";
export { ProgressMeter } from "./ProgressMeter";
export { Separator } from "./Separator";
export { StatusBadge } from "./StatusBadge";
export { ThemeToggle } from "./ThemeToggle";

View file

@ -0,0 +1,10 @@
export { CornerBracketFrame } from "./CornerBracketFrame";
export { DashboardFrame } from "./DashboardFrame";
export { DashboardHeader } from "./DashboardHeader";
export { DiagonalStripeField } from "./DiagonalStripeField";
export { FooterCell } from "./FooterCell";
export { FooterStatusCell } from "./FooterStatusCell";
export { GridFrame } from "./GridFrame";
export { ModuleCard } from "./ModuleCard";
export { Panel } from "./Panel";
export { ScanlineField } from "./ScanlineField";

View file

@ -0,0 +1,6 @@
export { ServiceGroupPanel } from "./ServiceGroupPanel";
export { ServicePanel } from "./ServicePanel";
export { ServiceRow } from "./ServiceRow";
export { StatusStrip } from "./StatusStrip";
export { SystemState } from "./SystemState";
export { WeatherModule } from "./WeatherModule";

View file

@ -0,0 +1,6 @@
export { LineChart } from "./LineChart";
export { SignalTrace } from "./SignalTrace";
export { Sparkline } from "./Sparkline";
export { TelemetryCard } from "./TelemetryCard";
export { TelemetryGrid } from "./TelemetryGrid";
export { TelemetryStrip } from "./TelemetryStrip";

View file

@ -1,3 +1,7 @@
export * from "./components/foundation";
export * from "./components/frames";
export * from "./components/operations";
export * from "./components/telemetry";
export { Badge } from "./components/foundation/Badge"; export { Badge } from "./components/foundation/Badge";
export { Button } from "./components/foundation/Button"; export { Button } from "./components/foundation/Button";
export { IconGlyph } from "./components/foundation/IconGlyph"; export { IconGlyph } from "./components/foundation/IconGlyph";

View file

@ -100,6 +100,43 @@ describe("Storybook inventory", () => {
} }
}); });
test("exposes grouped package entrypoints for each component domain", () => {
const packageJson = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8")) as {
exports?: Record<string, WorkspacePackageExport>;
};
const rootIndexSource = readFileSync(join(packageRoot, "src/index.ts"), "utf8");
const componentsByDomain = new Map<(typeof allowedComponentDomains)[number], string[]>();
for (const domain of allowedComponentDomains) {
componentsByDomain.set(domain, []);
}
for (const component of componentInventory()) {
componentsByDomain.get(component.domain)?.push(component.name);
}
for (const domain of allowedComponentDomains) {
const domainIndexPath = join(componentsDir, domain, "index.ts");
const domainIndexSource = existsSync(domainIndexPath)
? readFileSync(domainIndexPath, "utf8")
: "";
expect(existsSync(domainIndexPath), `${domain} index is missing`).toBe(true);
expect(rootIndexSource).toContain(`export * from "./components/${domain}";`);
expect(packageJson.exports?.[`./${domain}`]).toMatchObject({
types: `./dist/components/${domain}/index.d.ts`,
development: `./src/components/${domain}/index.ts`,
default: `./dist/components/${domain}/index.js`,
});
for (const componentName of componentsByDomain.get(domain) ?? []) {
expect(domainIndexSource).toContain(
`export { ${componentName} } from "./${componentName}";`,
);
}
}
});
test("keeps Storybook fixtures generic and content-free", () => { test("keeps Storybook fixtures generic and content-free", () => {
const storyText = readdirSync(storiesDir) const storyText = readdirSync(storiesDir)
.filter((filename) => filename.endsWith(".tsx") || filename.endsWith(".ts")) .filter((filename) => filename.endsWith(".tsx") || filename.endsWith(".ts"))
@ -174,12 +211,21 @@ function requiredComponentPath(component: string): string {
} }
interface ComponentInventoryItem { interface ComponentInventoryItem {
domain: (typeof allowedComponentDomains)[number];
name: string; name: string;
path: string; path: string;
relativeExportPath: string; relativeExportPath: string;
storyFile: string; storyFile: string;
} }
type WorkspacePackageExport =
| string
| {
types?: string;
development?: string;
default?: string;
};
function componentInventory(): ComponentInventoryItem[] { function componentInventory(): ComponentInventoryItem[] {
const components = allowedComponentDomains const components = allowedComponentDomains
.flatMap((domain) => collectComponentFiles(join(componentsDir, domain))) .flatMap((domain) => collectComponentFiles(join(componentsDir, domain)))
@ -208,12 +254,17 @@ function collectComponentFiles(directory: string): ComponentInventoryItem[] {
} }
const name = basename(entry.name, ".tsx"); const name = basename(entry.name, ".tsx");
const relativeComponentPath = relative(componentsDir, entryPath).replace(/\\/g, "/");
const domain = relativeComponentPath.split(
"/",
)[0] as (typeof allowedComponentDomains)[number];
const relativeExportPath = `./${relative(join(packageRoot, "src"), entryPath) const relativeExportPath = `./${relative(join(packageRoot, "src"), entryPath)
.replace(/\\/g, "/") .replace(/\\/g, "/")
.replace(/\.tsx$/, "")}`; .replace(/\.tsx$/, "")}`;
return [ return [
{ {
domain,
name, name,
path: entryPath, path: entryPath,
relativeExportPath, relativeExportPath,