diff --git a/apps/website/pages/components/status-light/code.tsx b/apps/website/pages/components/status-light/code.tsx new file mode 100644 index 0000000000..1823ff2378 --- /dev/null +++ b/apps/website/pages/components/status-light/code.tsx @@ -0,0 +1,17 @@ +import Head from "next/head"; +import type { ReactElement } from "react"; +import StatusLightPageLayout from "screens/components/status-light/StatusLightPageLayout"; +import StatusLightCodePage from "screens/components/status-light/code/StatusLightCodePage"; + +const Code = () => ( + <> + + Status light code — Halstack Design System + + + +); + +Code.getLayout = (page: ReactElement) => {page}; + +export default Code; diff --git a/apps/website/pages/components/status-light/index.tsx b/apps/website/pages/components/status-light/index.tsx index c5eb7c9d78..94c1a1e698 100644 --- a/apps/website/pages/components/status-light/index.tsx +++ b/apps/website/pages/components/status-light/index.tsx @@ -1,21 +1,17 @@ import Head from "next/head"; import type { ReactElement } from "react"; -import StatusLightCodePage from "screens/components/status-light/code/StatusLightCodePage"; +import StatusLightOverviewPage from "screens/components/status-light/overview/StatusLightOverviewPage"; import StatusLightPageLayout from "screens/components/status-light/StatusLightPageLayout"; -const Index = () => { - return ( - <> - - Status Light — Halstack Design System - - - - ); -}; +const Index = () => ( + <> + + Status light — Halstack Design System + + + +); -Index.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; +Index.getLayout = (page: ReactElement) => {page}; export default Index; diff --git a/apps/website/pages/components/status-light/specifications.tsx b/apps/website/pages/components/status-light/specifications.tsx deleted file mode 100644 index 421234d310..0000000000 --- a/apps/website/pages/components/status-light/specifications.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import Head from "next/head"; -import type { ReactElement } from "react"; -import StatusLightPageLayout from "screens/components/status-light/StatusLightPageLayout"; -import StatusLightSpecsPage from "screens/components/status-light/specs/StatusLightSpecsPage"; - -const Specifications = () => { - return ( - <> - - Status Light Specs — Halstack Design System - - - - ); -}; - -Specifications.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; - -export default Specifications; diff --git a/apps/website/pages/components/status-light/usage.tsx b/apps/website/pages/components/status-light/usage.tsx deleted file mode 100644 index d1ebef611d..0000000000 --- a/apps/website/pages/components/status-light/usage.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import Head from "next/head"; -import type { ReactElement } from "react"; -import StatusLightPageLayout from "screens/components/status-light/StatusLightPageLayout"; -import StatusLightUsagePage from "screens/components/status-light/usage/StatusLightUsagePage"; - -const Usage = () => { - return ( - <> - - Status Light Usage — Halstack Design System - - - - ); -}; - -Usage.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; - -export default Usage; diff --git a/apps/website/screens/components/status-light/StatusLightPageLayout.tsx b/apps/website/screens/components/status-light/StatusLightPageLayout.tsx index fbf4fcb7f8..cf437cd1de 100644 --- a/apps/website/screens/components/status-light/StatusLightPageLayout.tsx +++ b/apps/website/screens/components/status-light/StatusLightPageLayout.tsx @@ -6,21 +6,21 @@ import { ReactNode } from "react"; const StatusLightPageHeading = ({ children }: { children: ReactNode }) => { const tabs = [ - { label: "Code", path: "/components/status-light" }, - { label: "Usage", path: "/components/status-light/usage" }, - { label: "Specifications", path: "/components/status-light/specifications" }, + { label: "Overview", path: "/components/status-light" }, + { label: "Code", path: "/components/status-light/code" }, ]; return ( - + - Status lights, as semantic elements, allow the user to display the completion status of tasks, processes and - more. + Status light is a small, color-coded visual indicator used to represent the state or condition of a system, + process, or component. It provides at-a-glance feedback, helping users quickly assess statuses without + needing detailed explanations. - + {children} diff --git a/apps/website/screens/components/status-light/code/StatusLightCodePage.tsx b/apps/website/screens/components/status-light/code/StatusLightCodePage.tsx index 98c54d955f..e763d2ed75 100644 --- a/apps/website/screens/components/status-light/code/StatusLightCodePage.tsx +++ b/apps/website/screens/components/status-light/code/StatusLightCodePage.tsx @@ -21,16 +21,6 @@ const sections = [ - - mode - - 'default' | 'info' | 'success' | 'warning' | 'error' - - It will define the color of the light based on its semantic meaning. - - 'default' - - @@ -41,9 +31,19 @@ const sections = [ string - An auxiliar text that will add some context to the status. + An auxiliary text that will add some context to the status. - + + mode + + 'default' | 'info' | 'success' | 'warning' | 'error' + + It will define the color of the light based on its semantic meaning. + + 'default' + + size @@ -69,15 +69,13 @@ const sections = [ }, ]; -const StatusLightCodePage = () => { - return ( - - - - - - - ); -}; +const StatusLightCodePage = () => ( + + + + + + +); export default StatusLightCodePage; diff --git a/apps/website/screens/components/status-light/overview/StatusLightOverviewPage.tsx b/apps/website/screens/components/status-light/overview/StatusLightOverviewPage.tsx new file mode 100644 index 0000000000..f4976e0778 --- /dev/null +++ b/apps/website/screens/components/status-light/overview/StatusLightOverviewPage.tsx @@ -0,0 +1,137 @@ +import { DxcParagraph, DxcBulletedList, DxcFlex, DxcTable } from "@dxc-technology/halstack-react"; +import QuickNavContainer from "@/common/QuickNavContainer"; +import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; +import DocFooter from "@/common/DocFooter"; +import Example from "@/common/example/Example"; +import HeaderDescriptionCell from "@/common/HeaderDescriptionCell"; +import variants from "./example/variants"; +import anatomy from "./images/status_light_anatomy.png"; +import Image from "@/common/Image"; + +const sections = [ + { + title: "Introduction", + content: ( + + Being a non-clickable UI element, the status light is used to provide a quick, at-a-glance + indication of system states, alerts, or conditions within an interface. Designed for clarity and instant + recognition, it seamlessly integrates into various layouts without adding cognitive load. Status lights follow a + consistent color-coded system to ensure users can easily interpret their meaning. They are often used alongside + other components, such as tables, accordions, or dashboards, to enhance visibility and provide contextual + awareness. + + ), + }, + { + title: "Anatomy", + content: ( + <> + Status light's anatomy + + + Status light: the core visual element of a status light, designed as dot for clarity and + easy recognition. + + + Label: a short text description alongside the status light to provide additional context. + + + + ), + }, + { + title: "Variants", + content: ( + <> + + The status light component is available in five semantic variants, each represented by a distinct color. These + colors ensure clear communication of different states. + + + Additionally, the component comes in three different sizes, allowing for flexibility across various layouts + and screen sizes while maintaining readability and visual consistency. + + + + + + Variant + Description + + + + + + Default + + For neutral statuses, like archived, draft, paused... + + + + Info + + For live statuses, like active, in use, uploaded... + + + + Success + + For positive statuses, like finished, approved, completed... + + + + Warning + + For pending or critical statuses, like scheduled, in progress, processing... + + + + Error + + For negative statuses, like incomplete, rejected, failed... + + + + + ), + }, + { + title: "Best practices", + content: ( + + + Ensure semantic accuracy: always match each status light color with the correct meaning to + maintain clarity and avoid misinterpretation. + + + Optimize for different screen sizes: Select the appropriate size to ensure visibility and + legibility across various layouts. + + + Use clear and concise labels: Keep them brief and ensure they accurately describe the current + state. + + + Combine with badges carefully: status lights and semantic badges can only be used together if + one of them does not use a semantic color or if their semantic colors do not contradict each other. This + prevents misinterpretation and maintains clarity in data visualizations such as tables, charts, or grids. + + + Use strategically: overusing status lights in interfaces with high cognitive load can + overwhelm users and disrupt readability. Use them only where they add real value. + + + ), + }, +]; + +const StatusLightOverviewPage = () => ( + + + + + + +); + +export default StatusLightOverviewPage; diff --git a/apps/website/screens/components/status-light/usage/example/variants.ts b/apps/website/screens/components/status-light/overview/example/variants.ts similarity index 100% rename from apps/website/screens/components/status-light/usage/example/variants.ts rename to apps/website/screens/components/status-light/overview/example/variants.ts diff --git a/apps/website/screens/components/status-light/overview/images/status_light_anatomy.png b/apps/website/screens/components/status-light/overview/images/status_light_anatomy.png new file mode 100644 index 0000000000..e0f4fc3444 Binary files /dev/null and b/apps/website/screens/components/status-light/overview/images/status_light_anatomy.png differ diff --git a/apps/website/screens/components/status-light/specs/StatusLightSpecsPage.tsx b/apps/website/screens/components/status-light/specs/StatusLightSpecsPage.tsx deleted file mode 100644 index e767785daf..0000000000 --- a/apps/website/screens/components/status-light/specs/StatusLightSpecsPage.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { DxcBulletedList, DxcFlex, DxcParagraph } from "@dxc-technology/halstack-react"; -import Image from "@/common/Image"; -import QuickNavContainer from "@/common/QuickNavContainer"; -import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; -import Figure from "@/common/Figure"; -import DocFooter from "@/common/DocFooter"; -import specsImage from "./images/status-light_specs.jpg"; -import anatomyImage from "./images/status-light_anatomy.jpg"; - -const sections = [ - { - title: "Specifications", - content: ( -
- Status Light design specifications -
- ), - }, - { - title: "Anatomy", - content: ( - <> - Status Light anatomy - - Status Light - Label - - - ), - }, - { - title: "Design tokens", - content: ( - <> - This component currently has no design tokens. - - ), - }, -]; - -const StatusLightSpecsPage = () => { - return ( - - - - - - - ); -}; - -export default StatusLightSpecsPage; diff --git a/apps/website/screens/components/status-light/specs/images/status-light_anatomy.jpg b/apps/website/screens/components/status-light/specs/images/status-light_anatomy.jpg deleted file mode 100644 index 1c0f9195e6..0000000000 Binary files a/apps/website/screens/components/status-light/specs/images/status-light_anatomy.jpg and /dev/null differ diff --git a/apps/website/screens/components/status-light/specs/images/status-light_specs.jpg b/apps/website/screens/components/status-light/specs/images/status-light_specs.jpg deleted file mode 100644 index 42c95e4642..0000000000 Binary files a/apps/website/screens/components/status-light/specs/images/status-light_specs.jpg and /dev/null differ diff --git a/apps/website/screens/components/status-light/usage/StatusLightUsagePage.tsx b/apps/website/screens/components/status-light/usage/StatusLightUsagePage.tsx deleted file mode 100644 index fefd27c902..0000000000 --- a/apps/website/screens/components/status-light/usage/StatusLightUsagePage.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import { DxcParagraph, DxcBulletedList, DxcFlex, DxcTable } from "@dxc-technology/halstack-react"; -import QuickNavContainer from "@/common/QuickNavContainer"; -import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; -import DocFooter from "@/common/DocFooter"; -import Example from "@/common/example/Example"; -import HeaderDescriptionCell from "@/common/HeaderDescriptionCell"; -import variants from "./example/variants"; - -const sections = [ - { - title: "Usage", - content: ( - - The Status light component serves as a powerful communicator of an element or process' status. It focuses - exclusively on conveying semantic information within a range of five variants, each of them aligned with its - specific process status. - - ), - subSections: [ - { - title: "Do's", - content: ( - - - Match each status light variant with the correct semantic meaning to communicate clearly and consistently - messages about our statuses. - - - Use each size of the component according to the different screen sizes where it will be placed, paying - special attention to maintaining legibility and functionality. - - - Use concise and precise labels that reflect the status of a process accordingly. You should not use more - than three words in a status light label. - - - ), - }, - { - title: "Don'ts", - content: ( - - - Mix semantic meanings within Status lights, as this will lead to confusion and misinterpretation of - process states. - - - Use Status light and semantic badges in the same data visualization format (tables, charts, data - grids...). - - - Overuse the component. In screens with high cognitive load, you should not overuse this component, as it - may confuse users and interrupt the reading flow. - - - ), - }, - ], - }, - { - title: "Variants", - content: ( - <> - - The Status light component has five different modes, each one corresponding to its semantic meaning. - - - - - - Variant - Description - - - - - - Default - - For neutral statuses, like archived, draft, paused... - - - - Info - - For live statuses, like active, in use, uploaded... - - - - Success - - For positive statuses, like finished, approved, completed... - - - - Warning - - For pending or critical statuses, like scheduled, in progress, processing... - - - - Error - - For negative statuses, like incomplete, rejected, failed... - - - - - ), - }, -]; - -const StatusLightUsagePage = () => { - return ( - - - - - - - ); -}; - -export default StatusLightUsagePage; diff --git a/packages/lib/src/status-light/StatusLight.stories.tsx b/packages/lib/src/status-light/StatusLight.stories.tsx index 1521327fc9..6d5382f509 100644 --- a/packages/lib/src/status-light/StatusLight.stories.tsx +++ b/packages/lib/src/status-light/StatusLight.stories.tsx @@ -2,6 +2,7 @@ import { Meta, StoryObj } from "@storybook/react"; import ExampleContainer from "../../.storybook/components/ExampleContainer"; import Title from "../../.storybook/components/Title"; import DxcStatusLight from "./StatusLight"; +import DxcContainer from "../container/Container"; export default { title: "Status Light", @@ -70,6 +71,12 @@ const StatusLight = () => ( <DxcStatusLight label="StatusLight" mode="error" size="large" /> </ExampleContainer> + <ExampleContainer> + <Title title="Long label ellipsis" theme="light" level={4} /> + <DxcContainer width="200px"> + <DxcStatusLight label="Very long label to try to force ellipsis" /> + </DxcContainer> + </ExampleContainer> </> ); diff --git a/packages/lib/src/status-light/StatusLight.test.tsx b/packages/lib/src/status-light/StatusLight.test.tsx index 8f991653ca..a8b8f9d1a0 100644 --- a/packages/lib/src/status-light/StatusLight.test.tsx +++ b/packages/lib/src/status-light/StatusLight.test.tsx @@ -3,13 +3,11 @@ import DxcStatusLight from "./StatusLight"; describe("StatusLight component tests", () => { test("StatusLight renders with correct label", () => { - const { getByText } = render(<DxcStatusLight label="Status Light Test"></DxcStatusLight>); + const { getByText } = render(<DxcStatusLight label="Status Light Test" />); expect(getByText("Status Light Test")).toBeTruthy(); }); - - test("StatusLight applies accessibility attributes", () => { + test("StatusLight renders with role 'status'", () => { const { getByRole } = render(<DxcStatusLight label="Status Light Test" />); - const statusLightContainer = getByRole("status"); - expect(statusLightContainer.getAttribute("aria-label")).toBe("default: Status Light Test"); + expect(getByRole("status")).toBeTruthy(); }); }); diff --git a/packages/lib/src/status-light/StatusLight.tsx b/packages/lib/src/status-light/StatusLight.tsx index fb1973fb0c..06f498dcc5 100644 --- a/packages/lib/src/status-light/StatusLight.tsx +++ b/packages/lib/src/status-light/StatusLight.tsx @@ -1,71 +1,79 @@ import styled from "styled-components"; -import CoreTokens from "../common/coreTokens"; import StatusLightPropsType from "./types"; -const DxcStatusLight = ({ mode = "default", label, size = "medium" }: StatusLightPropsType): JSX.Element => { - return ( - <StatusLightContainer size={size} aria-label={`${mode}: ${label}`} role="status"> - <StatusDot mode={mode} size={size} aria-hidden="true" /> - <StatusLabel mode={mode} size={size}> - {label} - </StatusLabel> - </StatusLightContainer> - ); +const getModeColor = (mode: Required<StatusLightPropsType>["mode"]) => { + switch (mode) { + case "default": + return "var(--color-fg-neutral-strong);"; + case "error": + return "var(--color-fg-error-medium)"; + case "info": + return "var(--color-fg-secondary-medium)"; + case "success": + return "var(--color-fg-success-medium)"; + case "warning": + return "var(--color-fg-warning-strong)"; + } +}; + +const getSizeValues = (size: Required<StatusLightPropsType>["size"]) => { + switch (size) { + case "small": + return { + dotSize: "8px", + fontSize: "var(--typography-label-s)", + }; + case "medium": + return { + dotSize: "var(--height-xxxs)", + fontSize: "var(--typography-label-m)", + }; + case "large": + return { + dotSize: "var(--height-xxs)", + fontSize: "var(--typography-label-xl)", + }; + } }; -const StatusLightContainer = styled.div<{ size: StatusLightPropsType["size"] }>` +const StatusLightContainer = styled.div<{ size: Required<StatusLightPropsType>["size"] }>` display: inline-flex; align-items: center; - gap: ${CoreTokens.spacing_8}; + gap: ${({ size }) => (size === "small" ? "var(--spacing-gap-xs)" : "var(--spacing-gap-s)")}; + max-width: 100%; `; -const StatusDot = styled.div<{ - mode: StatusLightPropsType["mode"]; - size: StatusLightPropsType["size"]; +const Dot = styled.div<{ + mode: Required<StatusLightPropsType>["mode"]; + size: Required<StatusLightPropsType>["size"]; }>` - width: ${({ size }) => - (size === "small" && CoreTokens.type_scale_01) || - (size === "medium" && CoreTokens.type_scale_02) || - (size === "large" && CoreTokens.type_scale_03) || - CoreTokens.type_scale_02}; - height: ${({ size }) => - (size === "small" && CoreTokens.type_scale_01) || - (size === "medium" && CoreTokens.type_scale_02) || - (size === "large" && CoreTokens.type_scale_03) || - CoreTokens.type_scale_02}; + background-color: ${({ mode }) => getModeColor(mode)}; border-radius: 50%; - background-color: ${({ mode }) => - (mode === "default" && CoreTokens.color_grey_700) || - (mode === "error" && CoreTokens.color_red_700) || - (mode === "info" && CoreTokens.color_blue_700) || - (mode === "success" && CoreTokens.color_green_700) || - (mode === "warning" && CoreTokens.color_orange_700) || - CoreTokens.color_grey_700}; + flex-shrink: 0; + height: ${({ size }) => getSizeValues(size).dotSize}; + width: ${({ size }) => getSizeValues(size).dotSize}; `; -const StatusLabel = styled.span<{ - mode: StatusLightPropsType["mode"]; - size: StatusLightPropsType["size"]; +const Label = styled.span<{ + mode: Required<StatusLightPropsType>["mode"]; + size: Required<StatusLightPropsType>["size"]; }>` - font-size: ${({ size }) => - (size === "small" && CoreTokens.type_scale_01) || - (size === "medium" && CoreTokens.type_scale_02) || - (size === "large" && CoreTokens.type_scale_03) || - CoreTokens.type_scale_02}; - font-family: ${CoreTokens.type_sans}; - font-style: ${CoreTokens.type_normal}; - font-weight: ${CoreTokens.type_semibold}; - color: ${({ mode }) => - (mode === "default" && CoreTokens.color_grey_700) || - (mode === "error" && CoreTokens.color_red_700) || - (mode === "info" && CoreTokens.color_blue_700) || - (mode === "success" && CoreTokens.color_green_700) || - (mode === "warning" && CoreTokens.color_orange_700) || - CoreTokens.color_grey_700}; - text-align: center; - text-overflow: ellipsis; + color: ${({ mode }) => getModeColor(mode)}; + font-family: var(--typography-font-family); + font-size: ${({ size }) => getSizeValues(size).fontSize}; + font-weight: var(--typography-label-semibold); overflow: hidden; + text-overflow: ellipsis; white-space: nowrap; `; -export default DxcStatusLight; +export default function DxcStatusLight({ label, mode = "default", size = "medium" }: StatusLightPropsType) { + return ( + <StatusLightContainer role="status" size={size}> + <Dot mode={mode} size={size} aria-hidden="true" /> + <Label mode={mode} size={size}> + {label} + </Label> + </StatusLightContainer> + ); +}