From 0770f07581b59768bd4b09998f3fa8123cfb4992 Mon Sep 17 00:00:00 2001 From: Boram Yi Date: Tue, 8 Sep 2026 15:19:10 -0400 Subject: [PATCH 1/2] feat: add BuildInfoFooter component (SW-2383) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A subtle full-width footer bar for application build metadata โ€” app version and commit SHA. Both fields optional; only what you pass renders. Meant to sit at the bottom of a page/app. Content is right-aligned. Co-Authored-By: Claude Opus 4.8 --- .../BuildInfoFooter.stories.tsx | 48 ++++++++++++++++++ .../BuildInfoFooter/BuildInfoFooter.tsx | 49 +++++++++++++++++++ .../composed/BuildInfoFooter/index.ts | 2 + src/index.ts | 1 + 4 files changed, 100 insertions(+) create mode 100644 src/components/composed/BuildInfoFooter/BuildInfoFooter.stories.tsx create mode 100644 src/components/composed/BuildInfoFooter/BuildInfoFooter.tsx create mode 100644 src/components/composed/BuildInfoFooter/index.ts diff --git a/src/components/composed/BuildInfoFooter/BuildInfoFooter.stories.tsx b/src/components/composed/BuildInfoFooter/BuildInfoFooter.stories.tsx new file mode 100644 index 00000000..385857d8 --- /dev/null +++ b/src/components/composed/BuildInfoFooter/BuildInfoFooter.stories.tsx @@ -0,0 +1,48 @@ +import { expect, within } from "storybook/test"; + +import { BuildInfoFooter } from "./BuildInfoFooter"; + +import type { Meta, StoryObj } from "@storybook/react-vite"; + +const meta = { + title: "Design Patterns/Build Info Footer", + component: BuildInfoFooter, + parameters: { + layout: "fullscreen", + zephyr: { testCaseId: "" }, + }, + tags: ["autodocs"], + args: { + version: "1.4.2", + commitSha: "a1b2c3d", + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +/** App version + commit SHA. */ +export const Default: Story = { + parameters: { + zephyr: { testCaseId: "" }, + }, + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement); + + await step("renders the version and commit", async () => { + expect(canvasElement.querySelector('[data-slot="build-info-footer"]')).toBeInTheDocument(); + expect(canvas.getByText("v1.4.2")).toBeVisible(); + expect(canvas.getByText("a1b2c3d")).toBeVisible(); + }); + }, +}; + +/** Only what you pass renders โ€” here just the version. */ +export const VersionOnly: Story = { + args: { + commitSha: undefined, + }, + parameters: { + zephyr: { testCaseId: "" }, + }, +}; diff --git a/src/components/composed/BuildInfoFooter/BuildInfoFooter.tsx b/src/components/composed/BuildInfoFooter/BuildInfoFooter.tsx new file mode 100644 index 00000000..f863fc19 --- /dev/null +++ b/src/components/composed/BuildInfoFooter/BuildInfoFooter.tsx @@ -0,0 +1,49 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +export interface BuildInfoFooterProps + extends Omit, "children"> { + /** Application version (rendered as `v{version}`) */ + version?: string; + /** Short commit SHA */ + commitSha?: string; +} + +/** + * A subtle full-width footer bar for application build metadata โ€” the app + * version and commit SHA. Both fields are optional and only render when + * provided. Meant to sit at the bottom of a page/app. + */ +export function BuildInfoFooter({ + version, + commitSha, + className, + ...props +}: BuildInfoFooterProps) { + const meta = [version && `v${version}`, commitSha].filter( + (x): x is string => Boolean(x) + ); + + return ( +
+ {meta.map((item, i) => ( + + {i > 0 && ( + + ยท + + )} + {item} + + ))} +
+ ); +} diff --git a/src/components/composed/BuildInfoFooter/index.ts b/src/components/composed/BuildInfoFooter/index.ts new file mode 100644 index 00000000..91c9b861 --- /dev/null +++ b/src/components/composed/BuildInfoFooter/index.ts @@ -0,0 +1,2 @@ +export { BuildInfoFooter } from "./BuildInfoFooter"; +export type { BuildInfoFooterProps } from "./BuildInfoFooter"; diff --git a/src/index.ts b/src/index.ts index c9bfc202..63ddce8d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import "@/index.css"; // Composed +export * from "@/components/composed/BuildInfoFooter"; export * from "@/components/composed/FormPatterns"; export * from "@/components/composed/StatCard"; export * from "@/components/composed/DataAppShell"; From d806d406bd3d090f677544dc23873f0715e31f45 Mon Sep 17 00:00:00 2001 From: boramyi-ts Date: Tue, 8 Sep 2026 19:25:03 +0000 Subject: [PATCH 2/2] chore: add Zephyr test case IDs to stories --- .../composed/BuildInfoFooter/BuildInfoFooter.stories.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/composed/BuildInfoFooter/BuildInfoFooter.stories.tsx b/src/components/composed/BuildInfoFooter/BuildInfoFooter.stories.tsx index 385857d8..e213c711 100644 --- a/src/components/composed/BuildInfoFooter/BuildInfoFooter.stories.tsx +++ b/src/components/composed/BuildInfoFooter/BuildInfoFooter.stories.tsx @@ -24,7 +24,7 @@ type Story = StoryObj; /** App version + commit SHA. */ export const Default: Story = { parameters: { - zephyr: { testCaseId: "" }, + zephyr: { testCaseId: "SW-T5653" }, }, play: async ({ canvasElement, step }) => { const canvas = within(canvasElement); @@ -43,6 +43,6 @@ export const VersionOnly: Story = { commitSha: undefined, }, parameters: { - zephyr: { testCaseId: "" }, + zephyr: { testCaseId: "SW-T5654" }, }, };