refactor(ui): organize component source tree #35
66 changed files with 194 additions and 124 deletions
|
|
@ -16,7 +16,8 @@ validated dashboard model state inside the web app.
|
|||
- `packages/dashboard-model`: reusable dashboard schema, validation, and
|
||||
generic model fixtures shared by apps and tooling.
|
||||
- `packages/ui`: reusable dashboard React components, design tokens,
|
||||
shadcn/radix primitives, generic fixtures, and Storybook.
|
||||
shadcn/radix primitives, generic fixtures, and Storybook. Component source is
|
||||
grouped under `foundation`, `frames`, `operations`, and `telemetry` domains.
|
||||
- `docs/superpowers`: migration specs and execution plans used for this repo.
|
||||
|
||||
## Development
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { UiSeverity } from "../types";
|
||||
import type { UiSeverity } from "../../types";
|
||||
import { StatusBadge } from "./StatusBadge";
|
||||
|
||||
export interface BadgeProps {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import type { CSSProperties } from "react";
|
||||
import { clampPercent } from "../format";
|
||||
import type { UiSeverity } from "../types";
|
||||
import { clampPercent } from "../../format";
|
||||
import type { UiSeverity } from "../../types";
|
||||
|
||||
export interface ProgressMeterProps {
|
||||
value?: number;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import type { UiSeverity } from "../types";
|
||||
import type { UiSeverity } from "../../types";
|
||||
|
||||
export interface StatusBadgeProps {
|
||||
label: string;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import type { UiTheme } from "../theme";
|
||||
import { getNextUiTheme } from "../theme";
|
||||
import type { UiTheme } from "../../theme";
|
||||
import { getNextUiTheme } from "../../theme";
|
||||
import { IconGlyph } from "./IconGlyph";
|
||||
|
||||
export interface ThemeToggleProps {
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import { useId } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
import type { UiDashboardPreview } from "../types";
|
||||
import type { UiDashboardPreview } from "../../types";
|
||||
import { ModuleCard } from "./ModuleCard";
|
||||
import { ServicePanel } from "./ServicePanel";
|
||||
import { StatusStrip } from "./StatusStrip";
|
||||
import { TelemetryGrid } from "./TelemetryGrid";
|
||||
import { ServicePanel } from "../operations/ServicePanel";
|
||||
import { StatusStrip } from "../operations/StatusStrip";
|
||||
import { TelemetryGrid } from "../telemetry/TelemetryGrid";
|
||||
|
||||
export interface DashboardFrameProps {
|
||||
actions?: ReactNode;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { useId } from "react";
|
||||
import type { UiModuleBlock } from "../types";
|
||||
import type { UiModuleBlock } from "../../types";
|
||||
import { ModuleCard } from "./ModuleCard";
|
||||
|
||||
export interface DashboardHeaderProps {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import type { UiStatusItem } from "../types";
|
||||
import type { UiStatusItem } from "../../types";
|
||||
|
||||
export interface FooterCellProps {
|
||||
item: UiStatusItem;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import type { UiStatusItem } from "../types";
|
||||
import type { UiStatusItem } from "../../types";
|
||||
import { FooterCell } from "./FooterCell";
|
||||
|
||||
export function FooterStatusCell({ item }: { item: UiStatusItem }) {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { useId } from "react";
|
||||
import type { UiModuleBlock } from "../types";
|
||||
import { IconGlyph } from "./IconGlyph";
|
||||
import type { UiModuleBlock } from "../../types";
|
||||
import { IconGlyph } from "../foundation/IconGlyph";
|
||||
|
||||
export interface ModuleCardProps {
|
||||
module: UiModuleBlock;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import type { UiServiceGroup } from "../types";
|
||||
import type { UiServiceGroup } from "../../types";
|
||||
import { ServicePanel } from "./ServicePanel";
|
||||
|
||||
export function ServiceGroupPanel({ group }: { group: UiServiceGroup }) {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import type { UiServiceGroup } from "../types";
|
||||
import { Panel } from "./Panel";
|
||||
import type { UiServiceGroup } from "../../types";
|
||||
import { Panel } from "../frames/Panel";
|
||||
import { ServiceRow } from "./ServiceRow";
|
||||
import { StatusStrip } from "./StatusStrip";
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import type { UiServiceRow } from "../types";
|
||||
import { IconGlyph } from "./IconGlyph";
|
||||
import { StatusBadge } from "./StatusBadge";
|
||||
import type { UiServiceRow } from "../../types";
|
||||
import { IconGlyph } from "../foundation/IconGlyph";
|
||||
import { StatusBadge } from "../foundation/StatusBadge";
|
||||
|
||||
export interface ServiceRowProps {
|
||||
service: UiServiceRow;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import type { UiStatusItem } from "../types";
|
||||
import { FooterCell } from "./FooterCell";
|
||||
import type { UiStatusItem } from "../../types";
|
||||
import { FooterCell } from "../frames/FooterCell";
|
||||
|
||||
export interface StatusStripProps {
|
||||
id?: string;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import type { UiSeverity } from "../types";
|
||||
import { IconGlyph } from "./IconGlyph";
|
||||
import type { UiSeverity } from "../../types";
|
||||
import { IconGlyph } from "../foundation/IconGlyph";
|
||||
|
||||
export interface SystemStateProps {
|
||||
title: string;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import type { UiModuleBlock } from "../types";
|
||||
import { ModuleCard } from "./ModuleCard";
|
||||
import type { UiModuleBlock } from "../../types";
|
||||
import { ModuleCard } from "../frames/ModuleCard";
|
||||
|
||||
export function WeatherModule({ module }: { module: UiModuleBlock }) {
|
||||
return <ModuleCard module={module} />;
|
||||
|
|
@ -1,14 +1,16 @@
|
|||
import { renderToString } from "react-dom/server";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { Button } from "./Button";
|
||||
import { DashboardFrame } from "./DashboardFrame";
|
||||
import { FooterCell } from "./FooterCell";
|
||||
import { IconButton } from "./IconButton";
|
||||
import { ServiceRow } from "./ServiceRow";
|
||||
import { StatusStrip } from "./StatusStrip";
|
||||
import { TelemetryCard } from "./TelemetryCard";
|
||||
import { TelemetryGrid } from "./TelemetryGrid";
|
||||
import { ThemeToggle } from "./ThemeToggle";
|
||||
import {
|
||||
Button,
|
||||
DashboardFrame,
|
||||
FooterCell,
|
||||
IconButton,
|
||||
ServiceRow,
|
||||
StatusStrip,
|
||||
TelemetryCard,
|
||||
TelemetryGrid,
|
||||
ThemeToggle,
|
||||
} from "../index";
|
||||
import { dashboardPreviewFixtures } from "../fixtures";
|
||||
|
||||
describe("dashboard UI components", () => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import "uplot/dist/uPlot.min.css";
|
||||
import { useEffect, useRef } from "react";
|
||||
import type { UiSeverity } from "../types";
|
||||
import type { UiSeverity } from "../../types";
|
||||
|
||||
export interface LineChartProps {
|
||||
values?: number[];
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import type { UiSeverity } from "../types";
|
||||
import type { UiSeverity } from "../../types";
|
||||
|
||||
export interface SparklineProps {
|
||||
values?: number[];
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import type { CSSProperties } from "react";
|
||||
import { clampPercent, formatMetricValue } from "../format";
|
||||
import type { UiTelemetryCard } from "../types";
|
||||
import { IconGlyph } from "./IconGlyph";
|
||||
import { clampPercent, formatMetricValue } from "../../format";
|
||||
import type { UiTelemetryCard } from "../../types";
|
||||
import { IconGlyph } from "../foundation/IconGlyph";
|
||||
import { LineChart } from "./LineChart";
|
||||
|
||||
export interface TelemetryCardProps {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { useId } from "react";
|
||||
import type { UiTelemetryCard } from "../types";
|
||||
import type { UiTelemetryCard } from "../../types";
|
||||
import { TelemetryCard } from "./TelemetryCard";
|
||||
|
||||
export interface TelemetryGridProps {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import type { UiTelemetryCard } from "../types";
|
||||
import type { UiTelemetryCard } from "../../types";
|
||||
import { TelemetryGrid } from "./TelemetryGrid";
|
||||
|
||||
export function TelemetryStrip({ cards }: { cards: UiTelemetryCard[] }) {
|
||||
|
|
@ -26,15 +26,28 @@ const forbiddenTerms = [
|
|||
describe("UI package content boundary", () => {
|
||||
test("contains the reusable dashboard component inventory", () => {
|
||||
expect(existsSync(join(uiSourceRoot, "index.ts"))).toBe(true);
|
||||
expect(existsSync(join(uiSourceRoot, "components/DashboardFrame.tsx"))).toBe(
|
||||
true,
|
||||
);
|
||||
expect(existsSync(join(uiSourceRoot, "components/ThemeToggle.tsx"))).toBe(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
existsSync(join(uiSourceRoot, "components/frames/DashboardFrame.tsx")),
|
||||
).toBe(true);
|
||||
expect(
|
||||
existsSync(join(uiSourceRoot, "components/foundation/ThemeToggle.tsx")),
|
||||
).toBe(true);
|
||||
expect(existsSync(join(uiSourceRoot, "styles.css"))).toBe(true);
|
||||
});
|
||||
|
||||
test("keeps UI components grouped by domain instead of a flat bucket", () => {
|
||||
const domainFolders = ["foundation", "frames", "operations", "telemetry"];
|
||||
|
||||
for (const folder of domainFolders) {
|
||||
expect(existsSync(join(uiSourceRoot, "components", folder))).toBe(true);
|
||||
}
|
||||
|
||||
const flatComponentFiles = readdirSync(join(uiSourceRoot, "components"))
|
||||
.filter((entry) => entry.endsWith(".tsx") && !entry.endsWith(".test.tsx"))
|
||||
.sort();
|
||||
expect(flatComponentFiles).toEqual([]);
|
||||
});
|
||||
|
||||
test("keeps environment-specific content out of reusable UI source", () => {
|
||||
const source = readUiSource(uiSourceRoot).toLowerCase();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,33 +1,33 @@
|
|||
export { Badge } from "./components/Badge";
|
||||
export { Button } from "./components/Button";
|
||||
export { CornerBracketFrame } from "./components/CornerBracketFrame";
|
||||
export { DashboardHeader } from "./components/DashboardHeader";
|
||||
export { DashboardFrame } from "./components/DashboardFrame";
|
||||
export { DiagonalStripeField } from "./components/DiagonalStripeField";
|
||||
export { FooterCell } from "./components/FooterCell";
|
||||
export { FooterStatusCell } from "./components/FooterStatusCell";
|
||||
export { GridFrame } from "./components/GridFrame";
|
||||
export { IconGlyph } from "./components/IconGlyph";
|
||||
export { IconButton } from "./components/IconButton";
|
||||
export { LineChart } from "./components/LineChart";
|
||||
export { ModuleCard } from "./components/ModuleCard";
|
||||
export { Panel } from "./components/Panel";
|
||||
export { ProgressMeter } from "./components/ProgressMeter";
|
||||
export { Separator } from "./components/Separator";
|
||||
export { ServiceGroupPanel } from "./components/ServiceGroupPanel";
|
||||
export { ServicePanel } from "./components/ServicePanel";
|
||||
export { ServiceRow } from "./components/ServiceRow";
|
||||
export { ScanlineField } from "./components/ScanlineField";
|
||||
export { SignalTrace } from "./components/SignalTrace";
|
||||
export { Sparkline } from "./components/Sparkline";
|
||||
export { StatusBadge } from "./components/StatusBadge";
|
||||
export { StatusStrip } from "./components/StatusStrip";
|
||||
export { SystemState } from "./components/SystemState";
|
||||
export { TelemetryCard } from "./components/TelemetryCard";
|
||||
export { TelemetryGrid } from "./components/TelemetryGrid";
|
||||
export { TelemetryStrip } from "./components/TelemetryStrip";
|
||||
export { ThemeToggle } from "./components/ThemeToggle";
|
||||
export { WeatherModule } from "./components/WeatherModule";
|
||||
export { Badge } from "./components/foundation/Badge";
|
||||
export { Button } from "./components/foundation/Button";
|
||||
export { IconGlyph } from "./components/foundation/IconGlyph";
|
||||
export { IconButton } from "./components/foundation/IconButton";
|
||||
export { ProgressMeter } from "./components/foundation/ProgressMeter";
|
||||
export { Separator } from "./components/foundation/Separator";
|
||||
export { StatusBadge } from "./components/foundation/StatusBadge";
|
||||
export { ThemeToggle } from "./components/foundation/ThemeToggle";
|
||||
export { CornerBracketFrame } from "./components/frames/CornerBracketFrame";
|
||||
export { DashboardHeader } from "./components/frames/DashboardHeader";
|
||||
export { DashboardFrame } from "./components/frames/DashboardFrame";
|
||||
export { DiagonalStripeField } from "./components/frames/DiagonalStripeField";
|
||||
export { FooterCell } from "./components/frames/FooterCell";
|
||||
export { FooterStatusCell } from "./components/frames/FooterStatusCell";
|
||||
export { GridFrame } from "./components/frames/GridFrame";
|
||||
export { ModuleCard } from "./components/frames/ModuleCard";
|
||||
export { Panel } from "./components/frames/Panel";
|
||||
export { ScanlineField } from "./components/frames/ScanlineField";
|
||||
export { ServiceGroupPanel } from "./components/operations/ServiceGroupPanel";
|
||||
export { ServicePanel } from "./components/operations/ServicePanel";
|
||||
export { ServiceRow } from "./components/operations/ServiceRow";
|
||||
export { StatusStrip } from "./components/operations/StatusStrip";
|
||||
export { SystemState } from "./components/operations/SystemState";
|
||||
export { WeatherModule } from "./components/operations/WeatherModule";
|
||||
export { LineChart } from "./components/telemetry/LineChart";
|
||||
export { SignalTrace } from "./components/telemetry/SignalTrace";
|
||||
export { Sparkline } from "./components/telemetry/Sparkline";
|
||||
export { TelemetryCard } from "./components/telemetry/TelemetryCard";
|
||||
export { TelemetryGrid } from "./components/telemetry/TelemetryGrid";
|
||||
export { TelemetryStrip } from "./components/telemetry/TelemetryStrip";
|
||||
export { dashboardPreviewFixtures } from "./fixtures";
|
||||
export {
|
||||
getNextUiTheme,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { Badge } from "../components/Badge";
|
||||
import { Badge } from "../index";
|
||||
|
||||
const meta = {
|
||||
title: "UI/Badge",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { Button } from "../components/Button";
|
||||
import { Button } from "../index";
|
||||
|
||||
const meta = {
|
||||
title: "UI/Button",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { CornerBracketFrame } from "../components/CornerBracketFrame";
|
||||
import { CornerBracketFrame } from "../index";
|
||||
|
||||
const meta = {
|
||||
title: "UI/CornerBracketFrame",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { DashboardFrame } from "../components/DashboardFrame";
|
||||
import { DashboardFrame } from "../index";
|
||||
import { fullCompositionDashboard, secondaryDashboard } from "./story-data";
|
||||
|
||||
const meta = {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { DashboardHeader } from "../components/DashboardHeader";
|
||||
import { DashboardHeader } from "../index";
|
||||
import { moduleBlocks } from "./story-data";
|
||||
|
||||
const meta = {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { DashboardFrame } from "../components/DashboardFrame";
|
||||
import { DashboardFrame } from "../index";
|
||||
import { fullCompositionDashboard } from "./story-data";
|
||||
|
||||
const meta = {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { DiagonalStripeField } from "../components/DiagonalStripeField";
|
||||
import { DiagonalStripeField } from "../index";
|
||||
|
||||
const meta = {
|
||||
title: "UI/DiagonalStripeField",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { FooterCell } from "../components/FooterCell";
|
||||
import { FooterCell } from "../index";
|
||||
import { statusItems } from "./story-data";
|
||||
|
||||
const meta = {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { FooterStatusCell } from "../components/FooterStatusCell";
|
||||
import { FooterStatusCell } from "../index";
|
||||
import { statusItems } from "./story-data";
|
||||
|
||||
const meta = {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { GridFrame } from "../components/GridFrame";
|
||||
import { ModuleCard } from "../components/ModuleCard";
|
||||
import { GridFrame, ModuleCard } from "../index";
|
||||
import { moduleBlocks } from "./story-data";
|
||||
|
||||
const meta = {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { IconButton } from "../components/IconButton";
|
||||
import { IconButton } from "../index";
|
||||
|
||||
const meta = {
|
||||
title: "UI/IconButton",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { IconGlyph } from "../components/IconGlyph";
|
||||
import { IconGlyph } from "../index";
|
||||
|
||||
const meta = {
|
||||
title: "UI/IconGlyph",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { LineChart } from "../components/LineChart";
|
||||
import { LineChart } from "../index";
|
||||
|
||||
const meta = {
|
||||
title: "UI/LineChart",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { ModuleCard } from "../components/ModuleCard";
|
||||
import { ModuleCard } from "../index";
|
||||
import { moduleBlocks } from "./story-data";
|
||||
|
||||
const meta = {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { Panel } from "../components/Panel";
|
||||
import { ServiceRow } from "../components/ServiceRow";
|
||||
import { Panel, ServiceRow } from "../index";
|
||||
import { serviceRows } from "./story-data";
|
||||
|
||||
const meta = {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { ProgressMeter } from "../components/ProgressMeter";
|
||||
import { ProgressMeter } from "../index";
|
||||
|
||||
const meta = {
|
||||
title: "UI/ProgressMeter",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { ScanlineField } from "../components/ScanlineField";
|
||||
import { ScanlineField } from "../index";
|
||||
|
||||
const meta = {
|
||||
title: "UI/ScanlineField",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { Separator } from "../components/Separator";
|
||||
import { Separator } from "../index";
|
||||
|
||||
const meta = {
|
||||
title: "UI/Separator",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { ServiceGroupPanel } from "../components/ServiceGroupPanel";
|
||||
import { ServiceGroupPanel } from "../index";
|
||||
import { serviceGroups } from "./story-data";
|
||||
|
||||
const meta = {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { ServicePanel } from "../components/ServicePanel";
|
||||
import { ServicePanel } from "../index";
|
||||
import { serviceGroups } from "./story-data";
|
||||
|
||||
const meta = {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { ServiceRow } from "../components/ServiceRow";
|
||||
import { ServiceRow } from "../index";
|
||||
import { serviceRows } from "./story-data";
|
||||
|
||||
const meta = {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { SignalTrace } from "../components/SignalTrace";
|
||||
import { SignalTrace } from "../index";
|
||||
|
||||
const meta = {
|
||||
title: "UI/SignalTrace",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { Sparkline } from "../components/Sparkline";
|
||||
import { Sparkline } from "../index";
|
||||
|
||||
const meta = {
|
||||
title: "UI/Sparkline",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { StatusBadge } from "../components/StatusBadge";
|
||||
import { StatusBadge } from "../index";
|
||||
|
||||
const meta = {
|
||||
title: "UI/StatusBadge",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { StatusStrip } from "../components/StatusStrip";
|
||||
import { StatusStrip } from "../index";
|
||||
import { mixedStatusStrip } from "./story-data";
|
||||
|
||||
const meta = {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { SystemState } from "../components/SystemState";
|
||||
import { SystemState } from "../index";
|
||||
|
||||
const meta = {
|
||||
title: "UI/SystemState",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { TelemetryCard } from "../components/TelemetryCard";
|
||||
import { TelemetryCard } from "../index";
|
||||
import { telemetryCards } from "./story-data";
|
||||
|
||||
const meta = {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { TelemetryGrid } from "../components/TelemetryGrid";
|
||||
import { TelemetryGrid } from "../index";
|
||||
import { eightTelemetryCards, sixteenTelemetryCards } from "./story-data";
|
||||
|
||||
const meta = {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { TelemetryStrip } from "../components/TelemetryStrip";
|
||||
import { TelemetryStrip } from "../index";
|
||||
import { eightTelemetryCards } from "./story-data";
|
||||
|
||||
const meta = {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { ThemeToggle } from "../components/ThemeToggle";
|
||||
import { ThemeToggle } from "../index";
|
||||
|
||||
const meta = {
|
||||
title: "UI/ThemeToggle",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { WeatherModule } from "../components/WeatherModule";
|
||||
import { WeatherModule } from "../index";
|
||||
import { moduleBlocks } from "./story-data";
|
||||
|
||||
const meta = {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,56 @@ const packageRoot = existsSync(join(root, "packages/ui/package.json"))
|
|||
const componentsDir = join(packageRoot, "src/components");
|
||||
const storiesDir = join(packageRoot, "src/stories");
|
||||
|
||||
const componentDomains = {
|
||||
foundation: [
|
||||
"Badge",
|
||||
"Button",
|
||||
"IconButton",
|
||||
"IconGlyph",
|
||||
"ProgressMeter",
|
||||
"Separator",
|
||||
"StatusBadge",
|
||||
"ThemeToggle",
|
||||
],
|
||||
frames: [
|
||||
"CornerBracketFrame",
|
||||
"DashboardFrame",
|
||||
"DashboardHeader",
|
||||
"DiagonalStripeField",
|
||||
"FooterCell",
|
||||
"FooterStatusCell",
|
||||
"GridFrame",
|
||||
"ModuleCard",
|
||||
"Panel",
|
||||
"ScanlineField",
|
||||
],
|
||||
operations: [
|
||||
"ServiceGroupPanel",
|
||||
"ServicePanel",
|
||||
"ServiceRow",
|
||||
"StatusStrip",
|
||||
"SystemState",
|
||||
"WeatherModule",
|
||||
],
|
||||
telemetry: [
|
||||
"LineChart",
|
||||
"SignalTrace",
|
||||
"Sparkline",
|
||||
"TelemetryCard",
|
||||
"TelemetryGrid",
|
||||
"TelemetryStrip",
|
||||
],
|
||||
} as const;
|
||||
|
||||
const componentFiles: ReadonlyMap<string, string> = new Map(
|
||||
Object.entries(componentDomains).flatMap(([domain, components]) =>
|
||||
components.map((component) => [
|
||||
component,
|
||||
join(componentsDir, domain, `${component}.tsx`),
|
||||
]),
|
||||
),
|
||||
);
|
||||
|
||||
const requiredStoryFiles = [
|
||||
"Badge.stories.tsx",
|
||||
"Button.stories.tsx",
|
||||
|
|
@ -69,10 +119,7 @@ describe("Storybook inventory", () => {
|
|||
|
||||
test("loads dashboard component styles through the global app stylesheet", () => {
|
||||
const packageStyles = readFileSync(join(packageRoot, "src/styles.css"), "utf8");
|
||||
const dashboardFrame = readFileSync(
|
||||
join(componentsDir, "DashboardFrame.tsx"),
|
||||
"utf8",
|
||||
);
|
||||
const dashboardFrame = readFileSync(requiredComponentPath("DashboardFrame"), "utf8");
|
||||
|
||||
expect(packageStyles).toContain('./components/styles.css');
|
||||
expect(dashboardFrame).not.toContain('./styles.css');
|
||||
|
|
@ -87,9 +134,9 @@ describe("Storybook inventory", () => {
|
|||
});
|
||||
|
||||
test("keeps component and story files paired as the UI inventory changes", () => {
|
||||
const componentStoryFiles = readdirSync(componentsDir)
|
||||
.filter((filename) => filename.endsWith(".tsx") && !filename.endsWith(".test.tsx"))
|
||||
.map((filename) => filename.replace(".tsx", ".stories.tsx"));
|
||||
const componentStoryFiles = [...componentFiles.keys()].map(
|
||||
(component) => `${component}.stories.tsx`,
|
||||
);
|
||||
|
||||
for (const filename of componentStoryFiles) {
|
||||
expect(existsSync(join(storiesDir, filename)), `${filename} is missing`).toBe(true);
|
||||
|
|
@ -114,9 +161,7 @@ describe("Storybook inventory", () => {
|
|||
"ScanlineField",
|
||||
"SignalTrace",
|
||||
]) {
|
||||
expect(existsSync(join(componentsDir, `${component}.tsx`)), `${component} is missing`).toBe(
|
||||
true,
|
||||
);
|
||||
expect(existsSync(requiredComponentPath(component)), `${component} is missing`).toBe(true);
|
||||
expect(
|
||||
existsSync(join(storiesDir, `${component}.stories.tsx`)),
|
||||
`${component}.stories.tsx is missing`,
|
||||
|
|
@ -131,7 +176,7 @@ describe("Storybook inventory", () => {
|
|||
"ScanlineField",
|
||||
"SignalTrace",
|
||||
]) {
|
||||
const source = readFileSync(join(componentsDir, `${component}.tsx`), "utf8");
|
||||
const source = readFileSync(requiredComponentPath(component), "utf8");
|
||||
|
||||
expect(source).toContain('aria-hidden="true"');
|
||||
}
|
||||
|
|
@ -139,6 +184,7 @@ describe("Storybook inventory", () => {
|
|||
|
||||
test("does not add deferred form/navigation primitives", () => {
|
||||
for (const component of ["Input", "ToggleGroup", "ScrollArea"]) {
|
||||
expect(componentFiles.has(component)).toBe(false);
|
||||
expect(existsSync(join(componentsDir, `${component}.tsx`))).toBe(false);
|
||||
expect(existsSync(join(storiesDir, `${component}.stories.tsx`))).toBe(false);
|
||||
}
|
||||
|
|
@ -153,3 +199,13 @@ describe("Storybook inventory", () => {
|
|||
expect(legacyStories).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
function requiredComponentPath(component: string): string {
|
||||
const componentPath = componentFiles.get(component);
|
||||
|
||||
expect(componentPath, `${component} is missing from the UI component map`).toBeTypeOf(
|
||||
"string",
|
||||
);
|
||||
|
||||
return componentPath as string;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue