diff --git a/ecosystem-explorer/src/components/ui/detail-card.tsx b/ecosystem-explorer/src/components/ui/detail-card.tsx index 4602f626..0bb57fe5 100644 --- a/ecosystem-explorer/src/components/ui/detail-card.tsx +++ b/ecosystem-explorer/src/components/ui/detail-card.tsx @@ -14,12 +14,15 @@ * limitations under the License. */ import React from "react"; +import type { CollectorComponentType } from "./type-stripe-colors"; +import { TypeStripe } from "./type-stripe"; interface DetailCardProps { children: React.ReactNode; className?: string; withGrid?: boolean; withHoverEffect?: boolean; + typeStripe?: CollectorComponentType; } export function DetailCard({ @@ -27,6 +30,7 @@ export function DetailCard({ className = "", withGrid = false, withHoverEffect = false, + typeStripe, }: DetailCardProps) { const patternId = React.useId().replace(/:/g, "-"); @@ -38,6 +42,12 @@ export function DetailCard({ : "" } ${className}`} > + {typeStripe && ( + + )} {withGrid && (
diff --git a/ecosystem-explorer/src/components/ui/type-stripe-colors.ts b/ecosystem-explorer/src/components/ui/type-stripe-colors.ts new file mode 100644 index 00000000..48486e4c --- /dev/null +++ b/ecosystem-explorer/src/components/ui/type-stripe-colors.ts @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * TypeStripe color map — shared between the standalone `` + * primitive and the `typeStripe` slot on ``. Lives in its own + * file so React Fast Refresh stays happy (`react-refresh/only-export-components` + * disallows constants alongside component exports). + */ + +import type { CollectorComponent } from "@/types/collector"; + +export type CollectorComponentType = CollectorComponent["type"]; + +export const TYPE_STRIPE_COLORS: Record = { + receiver: "hsl(200 85% 45%)", + processor: "hsl(270 70% 55%)", + exporter: "hsl(38 95% 52%)", + connector: "hsl(330 75% 50%)", + extension: "hsl(165 70% 40%)", +}; diff --git a/ecosystem-explorer/src/components/ui/type-stripe.test.tsx b/ecosystem-explorer/src/components/ui/type-stripe.test.tsx new file mode 100644 index 00000000..f640d59a --- /dev/null +++ b/ecosystem-explorer/src/components/ui/type-stripe.test.tsx @@ -0,0 +1,45 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { render } from "@testing-library/react"; +import { describe, it, expect } from "vitest"; +import { type CollectorComponentType, TYPE_STRIPE_COLORS } from "./type-stripe-colors"; +import { TypeStripe } from "./type-stripe"; + +const types: CollectorComponentType[] = [ + "receiver", + "processor", + "exporter", + "connector", + "extension", +]; + +describe("TypeStripe", () => { + it.each(types)("renders an aria-hidden element with type=%s and the matching color", (type) => { + const { container } = render(); + const el = container.querySelector(".type-stripe"); + expect(el).not.toBeNull(); + expect(el).toHaveAttribute("data-type", type); + expect(el).toHaveAttribute("aria-hidden"); + expect(el).toHaveStyle({ backgroundColor: TYPE_STRIPE_COLORS[type] }); + }); + + it("exports a stable color for each of the five canonical types", () => { + expect(Object.keys(TYPE_STRIPE_COLORS).sort()).toEqual(types.slice().sort()); + for (const c of Object.values(TYPE_STRIPE_COLORS)) { + expect(c).toMatch(/^hsl\(/); + } + }); +}); diff --git a/ecosystem-explorer/src/components/ui/type-stripe.tsx b/ecosystem-explorer/src/components/ui/type-stripe.tsx new file mode 100644 index 00000000..6a674066 --- /dev/null +++ b/ecosystem-explorer/src/components/ui/type-stripe.tsx @@ -0,0 +1,49 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * TypeStripe — 4px left-edge color stripe used to flag the OTel-collector + * component type at the leading edge of cards, list rows, and detail headers. + * + * Five canonical types: receiver / processor / exporter / connector / + * extension. Color mapping lives in `./type-stripe-colors.ts` (split out so + * React Fast Refresh stays happy). + * + * Two consumers: + * 1. As a slot inside `` — DetailCard mounts + * `` with its own positioning className, so the stripe + * rendering stays in one place. + * 2. As a standalone `` for list rows that don't + * use DetailCard (e.g. compact list view in Phase 4). + */ + +import { type CollectorComponentType, TYPE_STRIPE_COLORS } from "./type-stripe-colors"; + +export interface TypeStripeProps { + type: CollectorComponentType; + className?: string; +} + +export function TypeStripe({ type, className }: TypeStripeProps) { + return ( + + ); +}