diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..32d1c14 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "packages/ui"] + path = packages/ui + url = ssh://git@git.dimensionlab.net/vince/dimensionlab-ui.git + branch = main diff --git a/README.md b/README.md index fee9443..00d9708 100644 --- a/README.md +++ b/README.md @@ -15,14 +15,16 @@ validated dashboard model state inside the web app. persistence, Playwright e2e checks, and container build. - `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. Component source is - grouped under `foundation`, `frames`, `operations`, and `telemetry` domains. +- `packages/ui`: Git submodule for the reusable dashboard React components, + design tokens, 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 ```sh +git submodule update --init --recursive bun install bun run dev ``` @@ -127,6 +129,7 @@ The production build emits Vite client assets under `apps/web/dist/` and a Bun server entry at `apps/web/build/index.js`. A minimal deployment flow is: ```sh +git submodule update --init --recursive bun install --frozen-lockfile bun run build cd apps/web diff --git a/apps/web/src/App.test.tsx b/apps/web/src/App.test.tsx index 71353f4..c319cc6 100644 --- a/apps/web/src/App.test.tsx +++ b/apps/web/src/App.test.tsx @@ -1,7 +1,8 @@ import { renderToString } from "react-dom/server"; import { describe, expect, test } from "vitest"; import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures"; -import { AppStateView } from "./App"; +import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab"; +import { AppStateView, dashboardHydrationTiles } from "./App"; describe("React app dashboard state view", () => { test("renders loading dashboard state", () => { @@ -54,7 +55,7 @@ describe("React app dashboard state view", () => { }} hydratingItemIds={new Set([ "telemetry:service-uptime", - "service:identity", + "service:core-services:identity", "module:ambient", "status:runtime:status", ])} @@ -73,4 +74,12 @@ describe("React app dashboard state view", () => { expect(html).toContain('data-severity="loading" data-model-id="ambient"'); expect(html).toContain('data-severity="loading" data-model-id="runtime:status"'); }); + + test("hydrates every status cell from the Dimension Lab shell", () => { + expect(dashboardHydrationTiles(dimensionLabDashboardFixture)).toContainEqual({ + kind: "status", + stripId: "footer-status", + id: "auto-refresh", + }); + }); }); diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 6a19bd5..206b74f 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -21,7 +21,7 @@ import { type DashboardTileReference = | { kind: "telemetry"; id: string } - | { kind: "service"; id: string } + | { kind: "service"; groupId: string; id: string } | { kind: "module"; id: string } | { kind: "status"; stripId: string; id: string }; @@ -295,7 +295,7 @@ function markHydratingItems( serviceGroups: dashboard.serviceGroups.map((group) => ({ ...group, services: group.services.map((service) => - hydratingItemIds.has(`service:${service.id}`) + hydratingItemIds.has(`service:${group.id}:${service.id}`) ? { ...service, severity: "loading", @@ -325,14 +325,20 @@ function markHydratingItems( }; } -function dashboardHydrationTiles(document: DashboardDocument): DashboardTileReference[] { +export function dashboardHydrationTiles( + document: DashboardDocument, +): DashboardTileReference[] { const telemetry = document.telemetry .filter((card) => card.datasource?.type === "external") .map((card): DashboardTileReference => ({ kind: "telemetry", id: card.id })); const services = document.serviceGroups.flatMap((group) => group.services .filter((service) => service.datasource?.type === "external") - .map((service): DashboardTileReference => ({ kind: "service", id: service.id })), + .map((service): DashboardTileReference => ({ + kind: "service", + groupId: group.id, + id: service.id, + })), ); const modules = (document.modules || []) .filter((module) => @@ -342,7 +348,6 @@ function dashboardHydrationTiles(document: DashboardDocument): DashboardTileRefe .map((module): DashboardTileReference => ({ kind: "module", id: module.id })); const status = document.statusStrips.flatMap((strip) => strip.items - .filter((item) => item.id !== "auto-refresh") .map((item): DashboardTileReference => ({ kind: "status", stripId: strip.id, @@ -354,15 +359,17 @@ function dashboardHydrationTiles(document: DashboardDocument): DashboardTileRefe } function dashboardTileKey(tile: DashboardTileReference): string { - return tile.kind === "status" - ? `${tile.kind}:${tile.stripId}:${tile.id}` - : `${tile.kind}:${tile.id}`; + if (tile.kind === "status") return `${tile.kind}:${tile.stripId}:${tile.id}`; + if (tile.kind === "service") return `${tile.kind}:${tile.groupId}:${tile.id}`; + return `${tile.kind}:${tile.id}`; } function dashboardTileUrl(tile: DashboardTileReference): string { const parts = tile.kind === "status" ? ["api", "dashboard", "tile", tile.kind, tile.stripId, tile.id] - : ["api", "dashboard", "tile", tile.kind, tile.id]; + : tile.kind === "service" + ? ["api", "dashboard", "tile", tile.kind, tile.groupId, tile.id] + : ["api", "dashboard", "tile", tile.kind, tile.id]; return `/${parts.map(encodeURIComponent).join("/")}`; } @@ -380,13 +387,16 @@ function applyDashboardTile( } if (response.tile.kind === "service") { + const tile = response.tile; return { ...document, serviceGroups: document.serviceGroups.map((group) => ({ ...group, - services: group.services.map((service) => - service.id === response.tile.id ? response.item as ServiceEntry : service, - ), + services: group.id === tile.groupId + ? group.services.map((service) => + service.id === tile.id ? response.item as ServiceEntry : service, + ) + : group.services, })), }; } diff --git a/apps/web/src/lib/server/datasources/index.ts b/apps/web/src/lib/server/datasources/index.ts index ff0c675..ee24bcd 100644 --- a/apps/web/src/lib/server/datasources/index.ts +++ b/apps/web/src/lib/server/datasources/index.ts @@ -12,7 +12,7 @@ import type { export type DashboardTileReference = | { kind: "telemetry"; id: string } - | { kind: "service"; id: string } + | { kind: "service"; groupId: string; id: string } | { kind: "module"; id: string } | { kind: "status"; stripId: string; id: string }; @@ -32,6 +32,11 @@ export type DashboardTileResolution = state: "not_found"; tile: DashboardTileReference; message: string; + } + | { + state: "disabled"; + tile: DashboardTileReference; + message: string; }; export interface DatasourceResolutionOptions { @@ -90,8 +95,8 @@ export async function resolveDashboardTile( if (tile.kind === "service") { const service = document.serviceGroups - .flatMap((group) => group.services) - .find((item) => item.id === tile.id); + .find((group) => group.id === tile.groupId) + ?.services.find((item) => item.id === tile.id); if (!service) return missingTile(tile); return { @@ -832,7 +837,7 @@ function missingTile(tile: DashboardTileReference): DashboardTileResolution { } function tileKey(tile: DashboardTileReference): string { - return tile.kind === "status" - ? `${tile.kind}:${tile.stripId}:${tile.id}` - : `${tile.kind}:${tile.id}`; + if (tile.kind === "status") return `${tile.kind}:${tile.stripId}:${tile.id}`; + if (tile.kind === "service") return `${tile.kind}:${tile.groupId}:${tile.id}`; + return `${tile.kind}:${tile.id}`; } diff --git a/apps/web/src/server/routes/dashboard.test.ts b/apps/web/src/server/routes/dashboard.test.ts index ae68e78..095ee48 100644 --- a/apps/web/src/server/routes/dashboard.test.ts +++ b/apps/web/src/server/routes/dashboard.test.ts @@ -113,6 +113,65 @@ describe("dashboard API route", () => { expect.objectContaining({ cache: "no-store" }), ); }); + + test("hydrates a service tile with its service group identity", async () => { + const fetch = vi.fn(async () => + jsonResponse({ + status: "UP", + ping: 42, + }), + ); + + const response = await loadDashboardTileResponse( + { kind: "service", groupId: "essentials", id: "vaultwarden" }, + { + fetch, + refreshSeedDocument: true, + seedIfEmpty: true, + }, + ); + + expect(response.state).toBe("ready"); + if (response.state !== "ready") throw new Error("expected ready tile"); + expect(response.tile).toEqual({ + kind: "service", + groupId: "essentials", + id: "vaultwarden", + }); + expect(response.item).toMatchObject({ + id: "vaultwarden", + severity: "ok", + detail: "42 ms", + }); + }); + + test("does not hydrate tile routes when live datasources are disabled", async () => { + const previous = process.env.DISABLE_LIVE_DATASOURCES; + process.env.DISABLE_LIVE_DATASOURCES = "1"; + const fetch = vi.spyOn(globalThis, "fetch").mockRejectedValue( + new Error("live datasource fetch should not run when disabled"), + ); + + try { + const response = await loadDashboardTileResponse( + { kind: "telemetry", id: "infra-ram" }, + { + refreshSeedDocument: true, + seedIfEmpty: true, + }, + ); + + expect(response.state).toBe("disabled"); + expect(fetch).not.toHaveBeenCalled(); + } finally { + fetch.mockRestore(); + if (previous === undefined) { + delete process.env.DISABLE_LIVE_DATASOURCES; + } else { + process.env.DISABLE_LIVE_DATASOURCES = previous; + } + } + }); }); function jsonResponse(payload: unknown): Response { diff --git a/apps/web/src/server/routes/dashboard.ts b/apps/web/src/server/routes/dashboard.ts index d4a6b72..fd0196e 100644 --- a/apps/web/src/server/routes/dashboard.ts +++ b/apps/web/src/server/routes/dashboard.ts @@ -63,6 +63,17 @@ export async function loadDashboardTileResponse( tile: DashboardTileReference, options: LoadDashboardResponseOptions = {}, ): Promise { + if ( + options.disableLiveDatasources || + process.env.DISABLE_LIVE_DATASOURCES === "1" + ) { + return { + state: "disabled", + tile, + message: "Live datasource hydration is disabled.", + }; + } + const dashboard = loadDashboardRuntime(undefined, { refreshSeedDocument: options.refreshSeedDocument ?? true, seedIfEmpty: options.seedIfEmpty ?? true, @@ -95,7 +106,7 @@ export async function handleDashboardTileRoute(pathname: string): Promise { - const theme = context.globals.theme === "light" ? "light" : "dark"; - - if (typeof document !== "undefined") { - document.documentElement.setAttribute("data-ui-theme", theme); - } - - return Story(); - }, - ], - globalTypes: { - theme: { - description: "Dashboard component theme", - defaultValue: "dark", - toolbar: { - title: "Theme", - icon: "circlehollow", - items: [ - { value: "dark", title: "Dark" }, - { value: "light", title: "Light" }, - ], - dynamicTitle: true, - }, - }, - }, - parameters: { - backgrounds: { - default: "canvas", - values: [ - { name: "canvas", value: "#0b0f0d" }, - { name: "raised", value: "#151d18" }, - { name: "light canvas", value: "#eef2e7" }, - { name: "light raised", value: "#f1f5ea" }, - ], - }, - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i, - }, - }, - layout: "fullscreen", - }, -}; - -export default preview; diff --git a/packages/ui/components.json b/packages/ui/components.json deleted file mode 100644 index d951369..0000000 --- a/packages/ui/components.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "$schema": "https://ui.shadcn.com/schema.json", - "style": "radix-nova", - "rsc": false, - "tsx": true, - "tailwind": { - "config": "", - "css": "src/styles.css", - "baseColor": "neutral", - "cssVariables": true, - "prefix": "" - }, - "iconLibrary": "lucide", - "rtl": false, - "aliases": { - "components": "src/components", - "utils": "src/utils", - "ui": "src/primitives", - "lib": "src", - "hooks": "src/hooks" - }, - "menuColor": "default", - "menuAccent": "subtle", - "registries": {} -} diff --git a/packages/ui/package.json b/packages/ui/package.json deleted file mode 100644 index a30db52..0000000 --- a/packages/ui/package.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "name": "@dimensionlab/ui", - "version": "0.0.1", - "private": true, - "type": "module", - "sideEffects": ["*.css", "**/*.css"], - "exports": { - ".": { - "types": "./dist/index.d.ts", - "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" - }, - "./tokens.css": { - "development": "./src/tokens.css", - "default": "./dist/tokens.css" - } - }, - "scripts": { - "build": "rm -rf dist && tsc -p tsconfig.build.json && mkdir -p dist/components && cp src/styles.css dist/styles.css && cp src/tokens.css dist/tokens.css && cp src/components/styles.css dist/components/styles.css", - "check": "tsc --noEmit", - "test": "vitest run", - "test:unit": "vitest run", - "storybook": "storybook dev -p 6006 --host 0.0.0.0", - "build-storybook": "storybook build" - }, - "dependencies": { - "@fontsource-variable/geist": "^5.2.9", - "@iconify/react": "^6.0.2", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "lucide-react": "^1.21.0", - "radix-ui": "^1.6.0", - "tailwind-merge": "^3.6.0", - "tw-animate-css": "^1.4.0", - "uplot": "^1.6.32" - }, - "peerDependencies": { - "react": "^19.0.0", - "react-dom": "^19.0.0" - }, - "devDependencies": { - "@storybook/addon-a11y": "^10.4.6", - "@storybook/addon-vitest": "^10.4.6", - "@storybook/react-vite": "^10.4.6", - "@types/bun": "^1.3.14", - "@types/node": "^25.9.3", - "@types/react": "^19.2.17", - "@types/react-dom": "^19.2.3", - "bun-types": "^1.3.14", - "storybook": "^10.4.6", - "typescript": "^6.0.3", - "vite": "^8.0.16", - "vitest": "^4.1.9" - } -} diff --git a/packages/ui/src/components/foundation/Badge.tsx b/packages/ui/src/components/foundation/Badge.tsx deleted file mode 100644 index 5bdee48..0000000 --- a/packages/ui/src/components/foundation/Badge.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import type { UiSeverity } from "../../types"; -import { StatusBadge } from "./StatusBadge"; - -export interface BadgeProps { - label: string; - severity?: UiSeverity; -} - -export function Badge({ label, severity = "neutral" }: BadgeProps) { - return ; -} diff --git a/packages/ui/src/components/foundation/Button.tsx b/packages/ui/src/components/foundation/Button.tsx deleted file mode 100644 index 8ae3b4e..0000000 --- a/packages/ui/src/components/foundation/Button.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import type { ButtonHTMLAttributes } from "react"; -import { IconGlyph } from "./IconGlyph"; - -export interface ButtonProps - extends Omit, "type"> { - label: string; - variant?: "primary" | "secondary" | "danger" | "ghost"; - size?: "default" | "compact"; - icon?: string; - loading?: boolean; - type?: "button" | "submit" | "reset"; -} - -export function Button({ - label, - variant = "primary", - size = "default", - icon, - disabled = false, - loading = false, - type = "button", - className = "", - ...buttonProps -}: ButtonProps) { - return ( - - ); -} diff --git a/packages/ui/src/components/foundation/IconButton.tsx b/packages/ui/src/components/foundation/IconButton.tsx deleted file mode 100644 index 92fcb46..0000000 --- a/packages/ui/src/components/foundation/IconButton.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import type { ButtonHTMLAttributes } from "react"; -import { IconGlyph } from "./IconGlyph"; - -export interface IconButtonProps - extends Omit, "type"> { - icon: string; - label: string; - active?: boolean; - type?: "button" | "submit" | "reset"; -} - -export function IconButton({ - icon, - label, - active = false, - disabled = false, - type = "button", - className = "", - ...buttonProps -}: IconButtonProps) { - return ( - - ); -} diff --git a/packages/ui/src/components/foundation/IconGlyph.tsx b/packages/ui/src/components/foundation/IconGlyph.tsx deleted file mode 100644 index d21feb1..0000000 --- a/packages/ui/src/components/foundation/IconGlyph.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Icon } from "@iconify/react"; - -export interface IconGlyphProps { - name?: string; - label?: string; - size?: "sm" | "md" | "lg"; -} - -export function IconGlyph({ name, label, size = "md" }: IconGlyphProps) { - return ( - - {name ? : null} - - ); -} diff --git a/packages/ui/src/components/foundation/ProgressMeter.tsx b/packages/ui/src/components/foundation/ProgressMeter.tsx deleted file mode 100644 index c3f3aad..0000000 --- a/packages/ui/src/components/foundation/ProgressMeter.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import type { CSSProperties } from "react"; -import { clampPercent } from "../../format"; -import type { UiSeverity } from "../../types"; - -export interface ProgressMeterProps { - value?: number; - severity?: UiSeverity; - label?: string; -} - -export function ProgressMeter({ - value, - severity = "neutral", - label = "Progress", -}: ProgressMeterProps) { - const progress = value === undefined ? null : clampPercent(value); - - return ( -
- {progress !== null ? ( - - ) : null} -
- ); -} diff --git a/packages/ui/src/components/foundation/Separator.tsx b/packages/ui/src/components/foundation/Separator.tsx deleted file mode 100644 index ca1a955..0000000 --- a/packages/ui/src/components/foundation/Separator.tsx +++ /dev/null @@ -1,19 +0,0 @@ -export interface SeparatorProps { - orientation?: "horizontal" | "vertical"; - dense?: boolean; -} - -export function Separator({ - orientation = "horizontal", - dense = false, -}: SeparatorProps) { - return ( -
- ); -} diff --git a/packages/ui/src/components/foundation/StatusBadge.tsx b/packages/ui/src/components/foundation/StatusBadge.tsx deleted file mode 100644 index d2b01b3..0000000 --- a/packages/ui/src/components/foundation/StatusBadge.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import type { UiSeverity } from "../../types"; - -export interface StatusBadgeProps { - label: string; - severity?: UiSeverity; -} - -export function StatusBadge({ label, severity = "neutral" }: StatusBadgeProps) { - return ( - - {label} - - ); -} diff --git a/packages/ui/src/components/foundation/ThemeToggle.tsx b/packages/ui/src/components/foundation/ThemeToggle.tsx deleted file mode 100644 index 5d83031..0000000 --- a/packages/ui/src/components/foundation/ThemeToggle.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import type { UiTheme } from "../../theme"; -import { getNextUiTheme } from "../../theme"; -import { IconGlyph } from "./IconGlyph"; - -export interface ThemeToggleProps { - theme: UiTheme; - onThemeChange: (theme: UiTheme) => void; -} - -export function ThemeToggle({ theme, onThemeChange }: ThemeToggleProps) { - const nextTheme = getNextUiTheme(theme); - - return ( - - ); -} diff --git a/packages/ui/src/components/foundation/index.ts b/packages/ui/src/components/foundation/index.ts deleted file mode 100644 index b82cd47..0000000 --- a/packages/ui/src/components/foundation/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export * from "./Badge"; -export * from "./Button"; -export * from "./IconButton"; -export * from "./IconGlyph"; -export * from "./ProgressMeter"; -export * from "./Separator"; -export * from "./StatusBadge"; -export * from "./ThemeToggle"; diff --git a/packages/ui/src/components/frames/CornerBracketFrame.tsx b/packages/ui/src/components/frames/CornerBracketFrame.tsx deleted file mode 100644 index 1dfd4f7..0000000 --- a/packages/ui/src/components/frames/CornerBracketFrame.tsx +++ /dev/null @@ -1,26 +0,0 @@ -export interface CornerBracketFrameProps { - density?: "regular" | "tight"; - size?: "sm" | "md" | "lg"; - tone?: "neutral" | "accent" | "danger"; -} - -export function CornerBracketFrame({ - density = "regular", - size = "md", - tone = "neutral", -}: CornerBracketFrameProps) { - return ( - - ); -} diff --git a/packages/ui/src/components/frames/DashboardFrame.tsx b/packages/ui/src/components/frames/DashboardFrame.tsx deleted file mode 100644 index 08029cf..0000000 --- a/packages/ui/src/components/frames/DashboardFrame.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { useId } from "react"; -import type { ReactNode } from "react"; -import type { UiDashboardPreview } from "../../types"; -import { ModuleCard } from "./ModuleCard"; -import { ServicePanel } from "../operations/ServicePanel"; -import { StatusStrip } from "../operations/StatusStrip"; -import { TelemetryGrid } from "../telemetry/TelemetryGrid"; - -export interface DashboardFrameProps { - actions?: ReactNode; - dashboard: UiDashboardPreview; - titleId?: string; -} - -export function DashboardFrame({ - actions, - dashboard, - titleId, -}: DashboardFrameProps) { - const generatedTitleId = useId(); - const resolvedTitleId = titleId || `${generatedTitleId}-title`; - - return ( -
-
-
-
- {dashboard.eyebrow ?

{dashboard.eyebrow}

: null} -

{dashboard.title}

- {dashboard.subtitle ? {dashboard.subtitle} : null} -
- {actions ? ( -
{actions}
- ) : null} -
- {dashboard.modules.length ? ( -
- {dashboard.modules.map((module) => ( - - ))} -
- ) : null} -
- - - -
- {dashboard.serviceGroups.map((group) => ( - - ))} -
- - -
- ); -} diff --git a/packages/ui/src/components/frames/DashboardHeader.tsx b/packages/ui/src/components/frames/DashboardHeader.tsx deleted file mode 100644 index 3f418fa..0000000 --- a/packages/ui/src/components/frames/DashboardHeader.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { useId } from "react"; -import type { UiModuleBlock } from "../../types"; -import { ModuleCard } from "./ModuleCard"; - -export interface DashboardHeaderProps { - title: string; - subtitle?: string; - eyebrow?: string; - module?: UiModuleBlock; -} - -export function DashboardHeader({ - title, - subtitle, - eyebrow, - module, -}: DashboardHeaderProps) { - const titleId = useId(); - - return ( -
-
- {eyebrow ?

{eyebrow}

: null} -

{title}

- {subtitle ? {subtitle} : null} -
- {module ? : null} -
- ); -} diff --git a/packages/ui/src/components/frames/DiagonalStripeField.tsx b/packages/ui/src/components/frames/DiagonalStripeField.tsx deleted file mode 100644 index b4c60f8..0000000 --- a/packages/ui/src/components/frames/DiagonalStripeField.tsx +++ /dev/null @@ -1,24 +0,0 @@ -export interface DiagonalStripeFieldProps { - density?: "open" | "regular" | "tight"; - direction?: "forward" | "backward"; - size?: "sm" | "md" | "lg"; - tone?: "neutral" | "accent" | "warning" | "danger"; -} - -export function DiagonalStripeField({ - density = "regular", - direction = "forward", - size = "md", - tone = "accent", -}: DiagonalStripeFieldProps) { - return ( -