diff --git a/.changeset/added-system-status.md b/.changeset/added-system-status.md new file mode 100644 index 00000000000..3e06b9e628f --- /dev/null +++ b/.changeset/added-system-status.md @@ -0,0 +1,13 @@ +--- +"@salt-ds/lab": minor +--- + +Added `SystemStatus`, `SystemStatusContent`, `SystemStatusActions` component to lab. + +```tsx + + + New feature updates are available + + +``` diff --git a/packages/lab/src/__tests__/__e2e__/system-status/SystemStatus.cy.tsx b/packages/lab/src/__tests__/__e2e__/system-status/SystemStatus.cy.tsx new file mode 100644 index 00000000000..a845bbd9851 --- /dev/null +++ b/packages/lab/src/__tests__/__e2e__/system-status/SystemStatus.cy.tsx @@ -0,0 +1,44 @@ +import { SystemStatus, SystemStatusContent } from "@salt-ds/lab"; +import * as systemStatusStories from "@stories/system-status/system-status.stories"; +import { composeStories } from "@storybook/react"; +import { checkAccessibility } from "../../../../../../cypress/tests/checkAccessibility"; + +const composedStories = composeStories(systemStatusStories); +// biome-ignore lint/suspicious/noShadowRestrictedNames: +const { Info, Success, Error, Warning } = composedStories; + +describe("GIVEN a System status", () => { + checkAccessibility(composedStories); + + it('should render an info status when `status="info"`', () => { + cy.mount(); + + cy.findByTestId("InfoSolidIcon").should("exist"); + }); + it('should render a success status when `status="success"`', () => { + cy.mount(); + + cy.findByTestId("SuccessTickIcon").should("exist"); + }); + it('should render a warning status when `status="warning"`', () => { + cy.mount(); + + cy.findByTestId("WarningSolidIcon").should("exist"); + }); + it('should render an error status when `status="error"`', () => { + cy.mount(); + + cy.findByTestId("ErrorSolidIcon").should("exist"); + }); + + it('should have a default role of "status"', () => { + const message = "example announcement"; + cy.mount( + + {message} + , + ); + + cy.findByRole("status").should("have.text", message); + }); +}); diff --git a/packages/lab/src/index.ts b/packages/lab/src/index.ts index ad7ba6f39c2..91cdff5c2f9 100644 --- a/packages/lab/src/index.ts +++ b/packages/lab/src/index.ts @@ -58,6 +58,7 @@ export * from "./skip-link"; export * from "./slider"; export * from "./stepped-tracker"; export * from "./stepper-input"; +export * from "./system-status"; export * from "./tabs"; export * from "./tabs-next"; export * from "./toast-group"; diff --git a/packages/lab/src/system-status/SystemStatus.css b/packages/lab/src/system-status/SystemStatus.css new file mode 100644 index 00000000000..526f4909cc6 --- /dev/null +++ b/packages/lab/src/system-status/SystemStatus.css @@ -0,0 +1,48 @@ +/* Styles applied to root component */ +.saltSystemStatus { + --saltIcon-color: var(--salt-content-bold-foreground); + + background: var(--saltSystemStatus-background, var(--systemStatus-background)); + border-color: var(--saltSystemStatus-borderColor, var(--systemStatus-borderColor)); + border-width: var(--saltSystemStatus-borderWidth, var(--salt-size-border)); + border-style: var(--saltSystemStatus-borderStyle, var(--salt-container-borderStyle)); + box-sizing: border-box; + display: flex; + gap: var(--salt-spacing-75); + padding: var(--saltSystemStatus-padding, var(--salt-spacing-50) var(--salt-spacing-100)); + width: 100%; + min-height: calc(var(--salt-size-base) + var(--salt-spacing-100)); + font-family: var(--salt-text-fontFamily); + font-size: var(--salt-text-fontSize); + font-weight: var(--salt-text-fontWeight); + line-height: var(--salt-text-lineHeight); +} + +/* Styles applied to icon */ +.saltSystemStatus-icon.saltIcon { + min-height: var(--salt-size-base); +} + +/* Styles applied when state = "info" */ +.saltSystemStatus-info { + --systemStatus-borderColor: var(--salt-status-info-bold-background); + --systemStatus-background: var(--salt-status-info-bold-background); +} + +/* Styles applied when state = "error" */ +.saltSystemStatus-error { + --systemStatus-borderColor: var(--salt-status-error-bold-background); + --systemStatus-background: var(--salt-status-error-bold-background); +} + +/* Styles applied when state = "warning" */ +.saltSystemStatus-warning { + --systemStatus-borderColor: var(--salt-status-warning-bold-background); + --systemStatus-background: var(--salt-status-warning-bold-background); +} + +/* Styles applied when state = "success" */ +.saltSystemStatus-success { + --systemStatus-borderColor: var(--salt-status-success-bold-background); + --systemStatus-background: var(--salt-status-success-bold-background); +} diff --git a/packages/lab/src/system-status/SystemStatus.tsx b/packages/lab/src/system-status/SystemStatus.tsx new file mode 100644 index 00000000000..c29bd76ef53 --- /dev/null +++ b/packages/lab/src/system-status/SystemStatus.tsx @@ -0,0 +1,47 @@ +import { + StatusIndicator, + type ValidationStatus, + makePrefixer, +} from "@salt-ds/core"; +import { clsx } from "clsx"; +import { type HTMLAttributes, forwardRef } from "react"; + +import { useComponentCssInjection } from "@salt-ds/styles"; +import { useWindow } from "@salt-ds/window"; + +import systemStatusCss from "./SystemStatus.css"; + +export interface SystemStatusProps extends HTMLAttributes { + /** + * A string to determine the current state of the SystemStatus. Defaults to `info`. + */ + status?: ValidationStatus; +} + +const withBaseName = makePrefixer("saltSystemStatus"); + +export const SystemStatus = forwardRef( + function SystemStatus( + { children, className, status = "info", ...rest }, + ref, + ) { + const targetWindow = useWindow(); + useComponentCssInjection({ + testId: "salt-system-status", + css: systemStatusCss, + window: targetWindow, + }); + + return ( +
+ + {children} +
+ ); + }, +); diff --git a/packages/lab/src/system-status/SystemStatusActions.css b/packages/lab/src/system-status/SystemStatusActions.css new file mode 100644 index 00000000000..ea8a17c43e0 --- /dev/null +++ b/packages/lab/src/system-status/SystemStatusActions.css @@ -0,0 +1,5 @@ +.saltSystemStatusActions { + margin-bottom: auto; + display: flex; + gap: var(--salt-spacing-100); +} diff --git a/packages/lab/src/system-status/SystemStatusActions.tsx b/packages/lab/src/system-status/SystemStatusActions.tsx new file mode 100644 index 00000000000..eb33199f044 --- /dev/null +++ b/packages/lab/src/system-status/SystemStatusActions.tsx @@ -0,0 +1,38 @@ +import { makePrefixer } from "@salt-ds/core"; +import { clsx } from "clsx"; +import { + type ComponentPropsWithoutRef, + type ReactNode, + forwardRef, +} from "react"; + +import { useComponentCssInjection } from "@salt-ds/styles"; +import { useWindow } from "@salt-ds/window"; +import systemStatusActionsCss from "./SystemStatusActions.css"; + +const withBaseName = makePrefixer("saltSystemStatusActions"); + +interface SystemStatusActionsProps extends ComponentPropsWithoutRef<"div"> { + /** + * The content of SystemStatusActions + */ + children: ReactNode; +} + +export const SystemStatusActions = forwardRef< + HTMLDivElement, + SystemStatusActionsProps +>(function SystemStatusActions(props, ref) { + const { className, ...rest } = props; + + const targetWindow = useWindow(); + useComponentCssInjection({ + testId: "salt-system-status-actions", + css: systemStatusActionsCss, + window: targetWindow, + }); + + return ( +
+ ); +}); diff --git a/packages/lab/src/system-status/SystemStatusContent.css b/packages/lab/src/system-status/SystemStatusContent.css new file mode 100644 index 00000000000..2351281408f --- /dev/null +++ b/packages/lab/src/system-status/SystemStatusContent.css @@ -0,0 +1,6 @@ +/* Styles applied to SystemStatusContent */ +.saltSystemStatusContent { + flex: 1 0; + margin: var(--salt-spacing-75) 0; + color: var(--salt-content-bold-foreground); +} diff --git a/packages/lab/src/system-status/SystemStatusContent.tsx b/packages/lab/src/system-status/SystemStatusContent.tsx new file mode 100644 index 00000000000..8c8b4392106 --- /dev/null +++ b/packages/lab/src/system-status/SystemStatusContent.tsx @@ -0,0 +1,38 @@ +import { makePrefixer } from "@salt-ds/core"; +import { clsx } from "clsx"; +import { + type ComponentPropsWithoutRef, + type ReactNode, + forwardRef, +} from "react"; + +import { useComponentCssInjection } from "@salt-ds/styles"; +import { useWindow } from "@salt-ds/window"; +import systemStatusContentCss from "./SystemStatusContent.css"; + +const withBaseName = makePrefixer("saltSystemStatusContent"); + +interface SystemStatusContentProps extends ComponentPropsWithoutRef<"div"> { + /** + * The content of SystemStatusContent + */ + children: ReactNode; +} + +export const SystemStatusContent = forwardRef< + HTMLDivElement, + SystemStatusContentProps +>(function SystemStatusContent(props, ref) { + const { className, ...rest } = props; + + const targetWindow = useWindow(); + useComponentCssInjection({ + testId: "salt-system-status-content", + css: systemStatusContentCss, + window: targetWindow, + }); + + return ( +
+ ); +}); diff --git a/packages/lab/src/system-status/index.tsx b/packages/lab/src/system-status/index.tsx new file mode 100644 index 00000000000..8f485878a5a --- /dev/null +++ b/packages/lab/src/system-status/index.tsx @@ -0,0 +1,3 @@ +export * from "./SystemStatus"; +export * from "./SystemStatusContent"; +export * from "./SystemStatusActions"; diff --git a/packages/lab/stories/system-status/system-status.qa.stories.tsx b/packages/lab/stories/system-status/system-status.qa.stories.tsx new file mode 100644 index 00000000000..8611559cedd --- /dev/null +++ b/packages/lab/stories/system-status/system-status.qa.stories.tsx @@ -0,0 +1,64 @@ +import { StackLayout, Text } from "@salt-ds/core"; +import { + SystemStatus, + SystemStatusContent, + type SystemStatusProps, +} from "@salt-ds/lab"; +import type { Meta, StoryFn } from "@storybook/react"; +import { QAContainer } from "docs/components"; +import type { FC } from "react"; + +export default { + title: "Lab/System Status/System Status QA", + component: SystemStatus, +} as Meta; + +const BasicSystemStatusExample: FC = ({ status }) => { + return ( + + + + + Title + + Example custom renderer + + + + ); +}; + +const InfoSystemStatus = () => ; +const ErrorSystemStatus = () => ; +const WarningSystemStatus = () => ( + +); +const SuccessSystemStatus = () => ( + +); + +export const ExamplesGrid: StoryFn = (props) => ( + + + + + + +); + +ExamplesGrid.parameters = { + chromatic: { + disableSnapshot: false, + modes: { + theme: { + themeNext: "disabled", + }, + themeNext: { + themeNext: "enable", + corner: "rounded", + accent: "teal", + // Ignore headingFont given font is not loaded + }, + }, + }, +}; diff --git a/packages/lab/stories/system-status/system-status.stories.tsx b/packages/lab/stories/system-status/system-status.stories.tsx new file mode 100644 index 00000000000..abbc77e72b5 --- /dev/null +++ b/packages/lab/stories/system-status/system-status.stories.tsx @@ -0,0 +1,112 @@ +import { + Banner, + BannerActions, + BannerContent, + Button, + StackLayout, + Text, +} from "@salt-ds/core"; +import { SystemStatus, SystemStatusContent } from "@salt-ds/lab"; +import type { Meta, StoryFn } from "@storybook/react"; + +import { CloseIcon } from "@salt-ds/icons"; + +export default { + title: "Lab/System Status", + component: SystemStatus, +} as Meta; + +export const Info: StoryFn = (props) => ( +
+ + + New feature updates are available + + +
+); + +export const Success: StoryFn = (props) => ( +
+ + + Your operation was completed successfully. + + +
+); + +export const Error: StoryFn = (props) => ( +
+ + + System failure. Please try again. + + +
+); + +export const Warning: StoryFn = (props) => ( +
+ + + + The system will be down for scheduled maintenance starting Friday, + June 21 from 11:00PM EST – 1:00AM EST Saturday, June 22 + + + +
+); + +export const WithTitle: StoryFn = (props) => ( +
+ + + + + Connection interrupted + + Please refresh the page. + + + +
+); + +export const Placement: StoryFn = (props) => { + return ( + + + + New feature updates are available. + + + + Payment Activity + + + You have outstanding checks more than 30 days old. Review to prevent + fraud. + + + + + + + + ); +}; diff --git a/site/docs/components/system-status/accessibility.mdx b/site/docs/components/system-status/accessibility.mdx new file mode 100644 index 00000000000..783a2aa30c4 --- /dev/null +++ b/site/docs/components/system-status/accessibility.mdx @@ -0,0 +1,16 @@ +--- +title: + $ref: ./#/title +layout: DetailComponent +sidebar: + exclude: true +data: + $ref: ./#/data +--- + +By default, the `SystemStatus` component has the role `"status"`. This means a screen reader will announce the message at the next available opportunity. + +## Best practices + +- If the status is critical and requires immediate attention, apply `role="alert"` to ensure the message is announced immediately. +- If the status is not critical, consider applying `role="region"`. diff --git a/site/docs/components/system-status/examples.mdx b/site/docs/components/system-status/examples.mdx new file mode 100644 index 00000000000..2dd129cdb96 --- /dev/null +++ b/site/docs/components/system-status/examples.mdx @@ -0,0 +1,64 @@ +--- +title: + $ref: ./#/title +layout: DetailComponent +sidebar: + exclude: true +data: + $ref: ./#/data +--- + + + + + +## Info + +Use the info system status to communicate important updates or new features that do not require immediate action. + + + + + +## Error + +Use error system status for critical issues that affect the user's ability to access or use the platform. When relevant, add a secondary action to help resolve the issue. + + + + + +## Warning + +Warnings announce scheduled outages or events that might impact the product experience, but don't prevent the user from using the application or completing their current workflow. + +Use warning system status to alert users about potential issues. + + + + + +## Success + +Use success system status to communicate successfully completed system actions. + + + + + +## With Title + +Use a title in a system status to highlight important information, and use description text to provide supporting details. + + + + + +## Placement + +System status alerts are displayed at the very top of the screen, extending across the full width of the page. + +They are positioned at the same level as the main content, causing any subsequent content to shift accordingly. For example, when a system status is dismissed, the content below it would move up to its original position. + + + diff --git a/site/docs/components/system-status/index.mdx b/site/docs/components/system-status/index.mdx new file mode 100644 index 00000000000..b63c006b258 --- /dev/null +++ b/site/docs/components/system-status/index.mdx @@ -0,0 +1,21 @@ +--- +title: System status +data: + description: "System status communicates essential platform-wide information or alerts, such as system maintenance or outages. These messages are designed to be quickly noticed and prompt users to take action if needed. System status offers 4 severity levels, each with a dedicated icon and color; `info`, `success`, `warning` and `error`. " + sourceCodeUrl: "https://github.com/jpmorganchase/salt-ds/blob/main/packages/lab/src/system-status" + package: + name: "@salt-ds/lab" + initialVersion: "1.0.0-alpha.48" + alsoKnownAs: ["System Message"] + relatedComponents: + [ + { name: "Content status", relationship: "similarTo" }, + { name: "Dialog", relationship: "similarTo" }, + { name: "Toast", relationship: "similarTo" }, + { name: "Tooltip", relationship: "similarTo" }, + { name: "Icon", relationship: "contains" }, + { name: "Status indicator", relationship: "contains" }, + ] + +layout: DetailComponent +--- diff --git a/site/docs/components/system-status/usage.mdx b/site/docs/components/system-status/usage.mdx new file mode 100644 index 00000000000..134cecf22d6 --- /dev/null +++ b/site/docs/components/system-status/usage.mdx @@ -0,0 +1,57 @@ +--- +title: + $ref: ./#/title +layout: DetailComponent +sidebar: + exclude: true +data: + $ref: ./#/data +--- + +## Using the component + +The primary purpose of the system status is to provide information and, if necessary, prompt further action. + +We advise against embedding excessive content into the system status due to potential color contrast issues. If you need to include additional components within the system status, it will be the responsibility of the application team to ensure accessibility compliance. + +### When to use + +- To inform users about critical system-wide issues, security updates or vulnerabilities that require immediate attention. +- To notify users about new system-wide features or enhancements. +- To alert users about planned maintenance schedules and potential service disruptions affecting the entire platform. + +### When not to use + +- For general updates or information that requires immediate user action. +- For inline messaging. Instead, use [`Banner`](../banner). +- To show a notification that applies to the user’s current task or workflow. Use [`Banner`](../banner) instead. +- To communicate when content is loading, instead use the Content Status pattern. +- When the notification requires immediate action and relates to the user’s current task. Instead, use [`Dialog`](../dialog) to interrupt the user's workflow. + +## Content + +A system status typically has one or two lines of text. Make content short, clear, and concise, allowing users to quickly understand the situation and/or next steps. + +Body Default applies to the content, however, if titles are necessary, configure them to display the Body Strong typography style across densities. See the [typography foundation](/salt/foundations/typography/) for more information. + +## Import + +To import `SystemStatus` and related components from the lab Salt package, use: + +``` +import { SystemStatus, SystemStatusContent, SystemStatusActions } from "@salt-ds/lab"; +``` + +## Props + +### `SystemStatus` + + + +### `SystemStatusContent` + + + +### `SystemStatusActions` + + diff --git a/site/src/examples/system-status/Error.tsx b/site/src/examples/system-status/Error.tsx new file mode 100644 index 00000000000..ea2f3c6eab3 --- /dev/null +++ b/site/src/examples/system-status/Error.tsx @@ -0,0 +1,13 @@ +import { Text } from "@salt-ds/core"; +import { SystemStatus, SystemStatusContent } from "@salt-ds/lab"; +import type { ReactElement } from "react"; + +export const Error = (): ReactElement => ( +
+ + + System failure. Please try again. + + +
+); diff --git a/site/src/examples/system-status/Info.tsx b/site/src/examples/system-status/Info.tsx new file mode 100644 index 00000000000..a91610c747c --- /dev/null +++ b/site/src/examples/system-status/Info.tsx @@ -0,0 +1,13 @@ +import { Text } from "@salt-ds/core"; +import { SystemStatus, SystemStatusContent } from "@salt-ds/lab"; +import type { ReactElement } from "react"; + +export const Info = (): ReactElement => ( +
+ + + New feature updates are available + + +
+); diff --git a/site/src/examples/system-status/Placement.tsx b/site/src/examples/system-status/Placement.tsx new file mode 100644 index 00000000000..e04917f3e91 --- /dev/null +++ b/site/src/examples/system-status/Placement.tsx @@ -0,0 +1,50 @@ +import { + Banner, + BannerActions, + BannerContent, + Button, + StackLayout, + Text, +} from "@salt-ds/core"; +import { SystemStatus, SystemStatusContent } from "@salt-ds/lab"; + +import { CloseIcon } from "@salt-ds/icons"; + +import type { ReactElement } from "react"; + +export const Placement = (): ReactElement => { + return ( + + + + New feature updates are available. + + + + Payment Activity + + + You have outstanding checks more than 30 days old. Review to prevent + fraud. + + + + + + + + ); +}; diff --git a/site/src/examples/system-status/Success.tsx b/site/src/examples/system-status/Success.tsx new file mode 100644 index 00000000000..f0ce71d6120 --- /dev/null +++ b/site/src/examples/system-status/Success.tsx @@ -0,0 +1,13 @@ +import { Text } from "@salt-ds/core"; +import { SystemStatus, SystemStatusContent } from "@salt-ds/lab"; +import type { ReactElement } from "react"; + +export const Success = (): ReactElement => ( +
+ + + Your operation was completed successfully. + + +
+); diff --git a/site/src/examples/system-status/Warning.tsx b/site/src/examples/system-status/Warning.tsx new file mode 100644 index 00000000000..73730523b0d --- /dev/null +++ b/site/src/examples/system-status/Warning.tsx @@ -0,0 +1,16 @@ +import { Text } from "@salt-ds/core"; +import { SystemStatus, SystemStatusContent } from "@salt-ds/lab"; +import type { ReactElement } from "react"; + +export const Warning = (): ReactElement => ( +
+ + + + The system will be down for scheduled maintenance starting Friday, + June 21 from 11:00PM EST – 1:00AM EST Saturday, June 22 + + + +
+); diff --git a/site/src/examples/system-status/WithTitle.tsx b/site/src/examples/system-status/WithTitle.tsx new file mode 100644 index 00000000000..d8a003eea85 --- /dev/null +++ b/site/src/examples/system-status/WithTitle.tsx @@ -0,0 +1,18 @@ +import { StackLayout, Text } from "@salt-ds/core"; +import { SystemStatus, SystemStatusContent } from "@salt-ds/lab"; +import type { ReactElement } from "react"; + +export const WithTitle = (): ReactElement => ( +
+ + + + + Connection interrupted + + Please refresh the page. + + + +
+); diff --git a/site/src/examples/system-status/index.ts b/site/src/examples/system-status/index.ts new file mode 100644 index 00000000000..a444f8d5216 --- /dev/null +++ b/site/src/examples/system-status/index.ts @@ -0,0 +1,6 @@ +export * from "./Error"; +export * from "./Info"; +export * from "./Success"; +export * from "./Warning"; +export * from "./WithTitle"; +export * from "./Placement";