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