Skip to content

feat(ui): add Status component - #1849

Open
franzheidl wants to merge 20 commits into
mainfrom
franz-status-1847
Open

feat(ui): add Status component#1849
franzheidl wants to merge 20 commits into
mainfrom
franz-status-1847

Conversation

@franzheidl

@franzheidl franzheidl commented Jul 30, 2026

Copy link
Copy Markdown
Member

Summary

This PR implements a generally usable Status component to render error, loading, no items, no matches etc.

Changes Made

  • add Status component, stories, tests

Related Issues

Closes #1828

Testing Instructions

  1. pnpm i
  2. pnpm run test Status

Checklist

  • I have performed a self-review of my code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • I have made corresponding changes to the documentation (if applicable).
  • My changes generate no new warnings or errors.
  • I have created a changeset for my changes.

PR Manifesto

Review the PR Manifesto for best practises.

Signed-off-by: Franz Heidl <franz.heidl@sap.com>
Signed-off-by: Franz Heidl <franz.heidl@sap.com>
Signed-off-by: Franz Heidl <franz.heidl@sap.com>
Signed-off-by: Franz Heidl <franz.heidl@sap.com>
Signed-off-by: Franz Heidl <franz.heidl@sap.com>
add `isDataGrid` var so elements can detect whether they are in a DataGrid or not

Signed-off-by: Franz Heidl <franz.heidl@sap.com>
Signed-off-by: Franz Heidl <franz.heidl@sap.com>
Signed-off-by: Franz Heidl <franz.heidl@sap.com>
Signed-off-by: Franz Heidl <franz.heidl@sap.com>
Signed-off-by: Franz Heidl <franz.heidl@sap.com>
Signed-off-by: Franz Heidl <franz.heidl@sap.com>
Signed-off-by: Franz Heidl <franz.heidl@sap.com>
Copilot AI review requested due to automatic review settings July 30, 2026 20:03
@changeset-bot

changeset-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: bcf227f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
@cloudoperators/juno-ui-components Patch
@cloudoperators/juno-app-carbon Patch
@cloudoperators/juno-app-doop Patch
@cloudoperators/juno-app-example Patch
@cloudoperators/juno-app-greenhouse Patch
@cloudoperators/juno-app-heureka Patch
@cloudoperators/juno-app-supernova Patch
@cloudoperators/juno-app-template Patch
@cloudoperators/juno-messages-provider Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

🚀 View preview at
https://cloudoperators.github.io/juno/pr-preview/pr-1849/

Built to branch gh-pages at 2026-07-31 13:56 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new reusable Status component to ui-components for displaying non-data states (progress, error, empty, no-matches), including DataGrid-aware rendering, with accompanying Storybook stories, tests, and theme tokens to support its visuals.

Changes:

  • Introduces Status component with defaults, optional spinner/details/action slots, and automatic compact styling when rendered inside a DataGrid.
  • Adds Storybook stories and a new test suite covering roles, defaults, spinner behavior, details, action slot, and DataGrid behavior.
  • Extends theme/global CSS tokens for Status details/code styling, and updates DataGrid context to expose an isDataGrid signal.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/ui-components/src/theme.css Adds theme tokens for Status details background/text/border and code color (light/dark).
packages/ui-components/src/global.css Mirrors Status-related tokens for global theming.
packages/ui-components/src/components/Status/Status.component.tsx Implements the Status component (defaults, HTTP code copy, DataGrid context behavior, styling).
packages/ui-components/src/components/Status/Status.test.tsx Adds unit tests for Status rendering behavior and DataGrid context behavior.
packages/ui-components/src/components/Status/Status.stories.tsx Adds Storybook docs + examples for page and DataGrid usage.
packages/ui-components/src/components/Status/index.ts Exports Status and StatusProps.
packages/ui-components/src/components/DataGrid/DataGrid.component.tsx Adds isDataGrid to context so consumer components can detect DataGrid presence.
Comments suppressed due to low confidence (2)

packages/ui-components/src/components/Status/Status.component.tsx:118

  • When code is provided but isn’t a known numeric HTTP code (e.g. the docs’ XXX unknown case, or any unmapped code), the component currently ignores the HTTP reference defaults entirely and falls back to the generic status defaults. Consider applying an explicit “Unknown Error” fallback whenever code is set but not matched.
  const numericCode = typeof code === "string" ? parseInt(code, 10) : code
  const httpDefaults = numericCode ? HTTP_ERRORS[numericCode] : undefined

packages/ui-components/src/components/Status/Status.component.tsx:13

  • The default copy for HTTP 404 doesn’t match the UX doc reference (it omits the guidance to check the URL / return home), so code={404} will render different text than specified.
  404: { title: "Page Not Found", body: "The requested URL does not exist or may have moved." },

Comment thread packages/ui-components/src/components/Status/Status.component.tsx Outdated
Comment thread packages/ui-components/src/components/Status/Status.component.tsx
Signed-off-by: Franz Heidl <franz.heidl@sap.com>
Copilot AI review requested due to automatic review settings July 30, 2026 20:15
Signed-off-by: Franz Heidl <franz.heidl@sap.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (3)

packages/ui-components/src/components/Status/Status.component.tsx:13

  • The HTTP error code reference in docs/ux/error-handling-loading-empty-states.md includes copy for 400 (Bad Request), but HTTP_ERRORS is missing a 400 entry, so code={400} won’t resolve documented defaults.
const HTTP_ERRORS: Record<number, { title: string; body: string }> = {
  400: { title: "Bad Request", body: "The request could not be processed due to invalid syntax. Try again." },
  401: { title: "Authentication Required", body: "Authentication failed. Verify your credentials and try again." },
  403: { title: "Access Denied", body: "You do not have the required permissions to access this resource." },

packages/ui-components/src/components/Status/Status.component.tsx:35

  • Status forwards ...props to the root <div> (and tests pass data-testid), but StatusProps does not extend HTMLAttributes<HTMLDivElement>. This makes common div props a TypeScript error for consumers.

export interface StatusProps extends React.HTMLAttributes<HTMLDivElement> {
  /** The status to display. Determines the default copy. Defaults to `"error"`. */

packages/ui-components/src/components/Status/Status.component.tsx:102

  • details is documented to “scroll vertically if content exceeds the maximum height”, but the base detailsStyles only sets horizontal scrolling. Outside a DataGrid, long details will expand the page instead of y-scrolling because there’s no max-height + overflow-y-auto.

const detailsStyles = `
  jn:text-left
  jn:text-xs
  jn:bg-theme-status-details
  jn:text-theme-status-details
  jn:border
  jn:border-theme-status-details
  jn:py-0.5
  jn:px-1
  jn:mt-4
  jn:w-full
  jn:max-w-[50rem]
  jn:overflow-x-auto

Copilot AI review requested due to automatic review settings July 30, 2026 20:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (3)

packages/ui-components/src/components/Status/Status.component.tsx:143

  • details can be rendered for any status, but the accessible label is always "Error details". This is misleading for non-error states (and for generic diagnostic output). Consider a status-aware label or a neutral label.
        <pre
          aria-label="Error details"
          className={`juno-status-details ${detailsStyles} ${isDataGrid ? detailsDataGridStyles : ""}`}

packages/ui-components/src/components/Status/Status.component.tsx:137

  • Spinner gets an empty aria-label when the resolved title is an empty string (e.g. status="no-matches" has a default title of "" and spinner={true} is allowed). An empty accessible name is invalid and makes the progress indicator harder to interpret for assistive tech. Use a falsy check instead of nullish coalescing so the fallback is used for "" as well.

This issue also appears on line 141 of the same file.

      {resolvedSpinner && <Spinner variant="primary" aria-label={resolvedTitle ?? "Loading"} />}

packages/ui-components/src/components/Status/Status.component.tsx:14

  • The default copy for HTTP 404 doesn’t match the UX docs reference (docs/ux/error-handling-loading-empty-states.md:148 includes the additional guidance "Check the URL or return to the home page."). Since the issue scope says error defaults should follow that reference, aligning the message here will keep Status consistent with the docs.
  404: { title: "Page Not Found", body: "The requested URL does not exist or may have moved." },

Signed-off-by: Franz Heidl <franz.heidl@sap.com>
Copilot AI review requested due to automatic review settings July 31, 2026 08:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Suppressed comments (3)

packages/ui-components/src/components/Status/Status.component.tsx:18

  • The 404 default body text here doesn’t match the UX docs’ HTTP error code reference (docs/ux/error-handling-loading-empty-states.md shows an additional “Check the URL or return to the home page.” sentence). This causes status="error" code={404} to render different copy than documented.
  403: { title: "Access Denied", body: "You do not have the required permissions to access this resource." },
  404: { title: "Page Not Found", body: "The requested URL does not exist or may have moved." },
  408: {
    title: "Request Timeout",

packages/ui-components/src/components/Status/Status.component.tsx:139

  • aria-label={resolvedTitle ?? "Loading"} can produce an empty accessible name when resolvedTitle is an empty string (e.g. the default no-matches title is ""). This leaves the Spinner/progressbar unlabeled for assistive tech when spinner is enabled.
      {code && !isDataGrid && <div className={`juno-status-code ${codeStyles}`}>{code}</div>}
      {resolvedSpinner && <Spinner variant="primary" aria-label={resolvedTitle ?? "Loading"} />}
      {resolvedTitle && <strong className={`juno-status-title ${titleStyles}`}>{resolvedTitle}</strong>}

packages/ui-components/src/components/Status/index.ts:6

  • Status is added with its own barrel export, but it isn’t exported from the package entrypoint (packages/ui-components/src/index.ts). As a result, consumers importing from @cloudoperators/juno-ui-components won’t be able to access the new component via the standard public API surface.
export { Status, type StatusProps } from "./Status.component"

Signed-off-by: Franz Heidl <franz.heidl@sap.com>
Signed-off-by: Franz Heidl <franz.heidl@sap.com>
Copilot AI review requested due to automatic review settings July 31, 2026 08:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Suppressed comments (4)

packages/ui-components/src/components/Status/Status.component.tsx:40

  • StatusProps extends React.HTMLAttributes<HTMLDivElement>, which already includes the standard HTML title attribute (tooltip). Reusing title for the component’s heading both overloads the meaning and prevents passing a real title attribute through to the root element (since it’s destructured out). Consider renaming the component prop (e.g., heading) and/or changing the props type to Omit<React.HTMLAttributes<HTMLDivElement>, "title"> to avoid the collision.
export interface StatusProps extends React.HTMLAttributes<HTMLDivElement> {
  /** The status to display. Determines the default copy. Defaults to `"error"`. */
  status?: "progress" | "error" | "empty" | "no-matches"
  /** Optional title. Overrides the per-status default title when set. */
  title?: string
  /** Optional body text. Overrides the per-status default body text when set. */
  body?: string

packages/ui-components/src/components/Status/Status.component.tsx:134

  • Because {...props} is spread after role={role}, a consumer-provided role will override the component’s computed role (and can unintentionally break the intended accessibility semantics and test expectations). To make the role deterministic, either spread props earlier (e.g., first) and then set role, or explicitly omit role from ...props.
  return (
    <div
      role={role}
      className={`juno-status ${isDataGrid ? `juno-status-datagrid ${statusDataGridStyles}` : ""} ${statusStyles} ${className}`}
      {...props}
    >

packages/ui-components/src/components/Status/Status.component.tsx:119

  • parseInt will accept partial numeric prefixes (e.g., "404 Not Found" becomes 404), which can unintentionally trigger HTTP defaults even when code is not a pure numeric code string. Consider only parsing when the string is strictly digits (or using Number() + Number.isFinite) so defaults are applied only for valid numeric HTTP codes.
  const numericCode = typeof code === "string" ? parseInt(code, 10) : code
  const httpDefaults = numericCode ? HTTP_ERRORS[numericCode] : undefined

packages/ui-components/src/components/Status/Status.component.tsx:32

  • STATUS_DEFAULTS is typed as Record<string, ...>, which removes compile-time guarantees that all supported status values are present (and only those). Tighten the type to the status union (e.g., Record<NonNullable<StatusProps["status"]>, ...> or satisfies Record<...>) to prevent drift if statuses are added/renamed later.
const STATUS_DEFAULTS: Record<string, { title: string; body: string }> = {
  progress: { title: "Loading…", body: "" },
  error: { title: "Something went wrong", body: "An error occurred. Try again." },
  empty: { title: "No items", body: "There are no items to display." },
  "no-matches": { title: "", body: "No items match the current filters. Adjust or clear filters." },
}

Signed-off-by: Franz Heidl <franz.heidl@sap.com>
Copilot AI review requested due to automatic review settings July 31, 2026 09:53
Signed-off-by: Franz Heidl <franz.heidl@sap.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 11 changed files in this pull request and generated no new comments.

Suppressed comments (3)

packages/ui-components/src/components/Status/Status.component.tsx:144

  • details can be rendered for any status, but the <pre> is always labeled "Error details". This is inaccurate for non-error statuses and can confuse assistive tech users. Consider using a neutral label or deriving it from status.
        <pre
          aria-label="Error details"
          className={`juno-status-details ${detailsStyles} ${isDataGrid ? detailsDataGridStyles : ""}`}
        >

docs/ux/datagrid.md:109

  • Typo in image alt text: "compponent" → "component".
![A DataGrid with a generic error rendered using the `Status` compponent](images/status-generic-error-datagrid.png)

packages/ui-components/src/components/Status/index.ts:6

  • Status is added under components/Status, but it isn’t exported from the package entrypoint (packages/ui-components/src/index.ts). As a result, consumers can’t import it from @cloudoperators/juno-ui-components the same way they do for other components.
export { Status, type StatusProps } from "./Status.component"

Copilot AI review requested due to automatic review settings July 31, 2026 09:57
@franzheidl
franzheidl marked this pull request as ready for review July 31, 2026 09:57
@franzheidl
franzheidl requested a review from a team as a code owner July 31, 2026 09:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 11 changed files in this pull request and generated no new comments.

Suppressed comments (7)

packages/ui-components/src/components/Status/Status.component.tsx:32

  • STATUS_DEFAULTS["no-matches"] sets an empty string title, which results in no title being rendered (because the render guard is resolvedTitle && ...). The issue/spec calls for sensible default title/body per status, so no-matches should also provide a non-empty default title.
const STATUS_DEFAULTS: Record<string, { title: string; body: string }> = {
  progress: { title: "Loading…", body: "" },
  error: { title: "Something went wrong", body: "An error occurred. Try again." },
  empty: { title: "No items", body: "There are no items to display." },
  "no-matches": { title: "", body: "No items match the current filters. Adjust or clear filters." },
}

packages/ui-components/src/components/Status/Status.test.tsx:75

  • This test currently asserts the 404 body text that’s implemented in HTTP_ERRORS, but that copy diverges from the UX docs’ HTTP error code reference. If the component is updated to match the documented 404 message, this expectation should be updated accordingly.
  it("renders title and body from HTTP error code", () => {
    render(<Status status="error" code={404} />)
    expect(screen.getByText("Page Not Found")).toBeInTheDocument()
    expect(screen.getByText("The requested URL does not exist or may have moved.")).toBeInTheDocument()
  })

packages/ui-components/src/components/Status/Status.component.tsx:144

  • details can be provided regardless of status, but the accessible label is hard-coded to “Error details”. This can be misleading for non-error use cases (and makes it harder to reuse the component). Use a neutral label like “Details”.
        <pre
          aria-label="Error details"
          className={`juno-status-details ${detailsStyles} ${isDataGrid ? detailsDataGridStyles : ""}`}
        >

packages/ui-components/src/components/Status/Status.test.tsx:96

  • If the Status component’s details block label is made neutral (e.g. “Details”), the test should assert the updated label instead of “Error details”.
  it("renders details in a pre element", () => {
    render(<Status details="Error: something failed" />)
    expect(screen.getByLabelText("Error details")).toBeInTheDocument()
    expect(screen.getByText("Error: something failed")).toBeInTheDocument()
  })

  it("does not render details when not passed", () => {
    render(<Status />)
    expect(screen.queryByLabelText("Error details")).not.toBeInTheDocument()
  })

packages/ui-components/src/components/Status/Status.component.tsx:15

  • The default copy for HTTP 404 does not match the UX docs’ HTTP error code reference (docs/ux/error-handling-loading-empty-states.md includes the additional guidance “Check the URL or return to the home page.”). Since the issue/PR states defaults should follow that reference, the 404 body should be aligned (or the docs updated).

This issue also appears in the following locations of the same file:

  • line 27
  • line 141
  403: { title: "Access Denied", body: "You do not have the required permissions to access this resource." },
  404: { title: "Page Not Found", body: "The requested URL does not exist or may have moved." },
  408: {

packages/ui-components/src/components/Status/Status.test.tsx:69

  • There’s no test covering the no-matches status defaults. Since Status has status-specific default copy, adding a no-matches assertion helps prevent regressions (especially after changing defaults).

This issue also appears in the following locations of the same file:

  • line 71
  • line 87
  it("renders the default title for the empty status", () => {
    render(<Status status="empty" />)
    expect(screen.getByRole("status")).toHaveTextContent("No items")
  })

docs/ux/datagrid.md:109

  • Typo in image alt text: “compponent” → “component”.
![A DataGrid with a generic error rendered using the `Status` compponent](images/status-generic-error-datagrid.png)

to be refined

Signed-off-by: Franz Heidl <franz.heidl@sap.com>
Copilot AI review requested due to automatic review settings July 31, 2026 13:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 11 changed files in this pull request and generated no new comments.

Suppressed comments (5)

packages/ui-components/src/components/Status/Status.component.tsx:137

  • Spinner's aria-label uses resolvedTitle ?? "Loading". For status="no-matches", the default title is an empty string, so if a caller enables spinner, the label becomes an empty string (because ?? does not treat "" as missing), which is not accessible. Use a fallback that treats empty strings as missing.
      {resolvedSpinner && <Spinner variant="primary" aria-label={resolvedTitle ?? "Loading"} />}

packages/ui-components/src/components/Status/Status.component.tsx:106

  • Status is implemented and has a local components/Status/index.ts, but it is not exported from the package entrypoint (packages/ui-components/src/index.ts). As a result, consumers importing from @cloudoperators/juno-ui-components won’t be able to use it without a deep import.
export const Status = ({
  status = "error",

packages/ui-components/src/components/Status/Status.test.tsx:70

  • There is no test covering the no-matches default copy/structure (notably the intentional empty default title). Adding a test will prevent regressions where a blank title accidentally renders, or the default body copy changes unexpectedly.
  it("renders the default title for the empty status", () => {
    render(<Status status="empty" />)
    expect(screen.getByRole("status")).toHaveTextContent("No items")
  })

docs/ux/datagrid.md:109

  • Typo in the image alt text: "compponent" → "component".
![A DataGrid with a generic error rendered using the `Status` compponent](images/status-generic-error-datagrid.png)

packages/ui-components/src/components/Status/Status.component.tsx:103

  • details is documented to “scroll vertically if content exceeds the maximum height”, but outside a DataGrid the <pre> has no max-height and no overflow-y-auto, so long stack traces will expand the page instead of scrolling. Consider adding a max-height + vertical scrolling to the base detailsStyles (and keep the DataGrid-specific min-h-0 if needed for flexbox scrolling).
const detailsStyles = `
  jn:text-left
  jn:text-xs
  jn:bg-theme-status-details
  jn:text-theme-status-details
  jn:border
  jn:border-theme-status-details
  jn:py-0.5
  jn:px-1
  jn:mt-4
  jn:w-full
  jn:max-w-[50rem]
  jn:overflow-x-auto
`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Task](ui): implement Status component

2 participants