From 6a0c57e93cbf6cb9cbf4b7504d1faae2c90839c1 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 20 Jun 2026 07:31:35 +0200 Subject: [PATCH] feat(ui): expose grouped component entrypoints --- package.json | 20 ++++++++++++ src/components/foundation/index.ts | 8 +++++ src/components/frames/index.ts | 10 ++++++ src/components/operations/index.ts | 6 ++++ src/components/telemetry/index.ts | 6 ++++ src/index.ts | 4 +++ src/storybook.test.ts | 51 ++++++++++++++++++++++++++++++ 7 files changed, 105 insertions(+) create mode 100644 src/components/foundation/index.ts create mode 100644 src/components/frames/index.ts create mode 100644 src/components/operations/index.ts create mode 100644 src/components/telemetry/index.ts diff --git a/package.json b/package.json index 897043c..a30db52 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,26 @@ "development": "./src/index.ts", "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": { "development": "./src/styles.css", "default": "./dist/styles.css" diff --git a/src/components/foundation/index.ts b/src/components/foundation/index.ts new file mode 100644 index 0000000..36ed868 --- /dev/null +++ b/src/components/foundation/index.ts @@ -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"; diff --git a/src/components/frames/index.ts b/src/components/frames/index.ts new file mode 100644 index 0000000..66a48c9 --- /dev/null +++ b/src/components/frames/index.ts @@ -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"; diff --git a/src/components/operations/index.ts b/src/components/operations/index.ts new file mode 100644 index 0000000..27ab623 --- /dev/null +++ b/src/components/operations/index.ts @@ -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"; diff --git a/src/components/telemetry/index.ts b/src/components/telemetry/index.ts new file mode 100644 index 0000000..4ba32fa --- /dev/null +++ b/src/components/telemetry/index.ts @@ -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"; diff --git a/src/index.ts b/src/index.ts index bc506c0..e1fe97e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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 { Button } from "./components/foundation/Button"; export { IconGlyph } from "./components/foundation/IconGlyph"; diff --git a/src/storybook.test.ts b/src/storybook.test.ts index 83f51c7..050170b 100644 --- a/src/storybook.test.ts +++ b/src/storybook.test.ts @@ -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; + }; + 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", () => { const storyText = readdirSync(storiesDir) .filter((filename) => filename.endsWith(".tsx") || filename.endsWith(".ts")) @@ -174,12 +211,21 @@ function requiredComponentPath(component: string): string { } interface ComponentInventoryItem { + domain: (typeof allowedComponentDomains)[number]; name: string; path: string; relativeExportPath: string; storyFile: string; } +type WorkspacePackageExport = + | string + | { + types?: string; + development?: string; + default?: string; + }; + function componentInventory(): ComponentInventoryItem[] { const components = allowedComponentDomains .flatMap((domain) => collectComponentFiles(join(componentsDir, domain))) @@ -208,12 +254,17 @@ function collectComponentFiles(directory: string): ComponentInventoryItem[] { } 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) .replace(/\\/g, "/") .replace(/\.tsx$/, "")}`; return [ { + domain, name, path: entryPath, relativeExportPath,