diff --git a/lib/src/main.ts b/lib/src/main.ts index c48d619b9..5eae38b86 100644 --- a/lib/src/main.ts +++ b/lib/src/main.ts @@ -42,6 +42,7 @@ import DxcBulletedList from "./bulleted-list/BulletedList"; import DxcGrid from "./grid/Grid"; import DxcImage from "./image/Image"; import DxcContainer from "./container/Container"; +import DxcStatusLight from "./status-light/StatusLight"; import HalstackContext, { HalstackProvider, HalstackLanguageContext } from "./HalstackContext"; @@ -93,4 +94,5 @@ export { DxcGrid, DxcImage, DxcContainer, + DxcStatusLight, }; diff --git a/lib/src/status-light/StatusLight.stories.tsx b/lib/src/status-light/StatusLight.stories.tsx new file mode 100644 index 000000000..c4cd3720c --- /dev/null +++ b/lib/src/status-light/StatusLight.stories.tsx @@ -0,0 +1,74 @@ +import React from "react"; +import Title from "../../.storybook/components/Title"; +import ExampleContainer from "../../.storybook/components/ExampleContainer"; +import DxcStatusLight from "./StatusLight"; + +export default { + title: "Status Light", + component: DxcStatusLight, +}; + +export const Chromatic = () => ( + <> + + + <DxcStatusLight label="StatusLight" size="small" /> + </ExampleContainer> + <ExampleContainer> + <Title title="Default light medium" theme="light" level={4} /> + <DxcStatusLight label="StatusLight" /> + </ExampleContainer> + <ExampleContainer> + <Title title="Default light large" theme="light" level={4} /> + <DxcStatusLight label="StatusLight" size="large" /> + </ExampleContainer> + <ExampleContainer> + <Title title="Info light small" theme="light" level={4} /> + <DxcStatusLight label="StatusLight" mode="info" size="small" /> + </ExampleContainer> + <ExampleContainer> + <Title title="Info light medium" theme="light" level={4} /> + <DxcStatusLight label="StatusLight" mode="info" /> + </ExampleContainer> + <ExampleContainer> + <Title title="Info light large" theme="light" level={4} /> + <DxcStatusLight label="StatusLight" mode="info" size="large" /> + </ExampleContainer> + <ExampleContainer> + <Title title="Success light small" theme="light" level={4} /> + <DxcStatusLight label="StatusLight" mode="success" size="small" /> + </ExampleContainer> + <ExampleContainer> + <Title title="Success lights medium" theme="light" level={4} /> + <DxcStatusLight label="StatusLight" mode="success" /> + </ExampleContainer> + <ExampleContainer> + <Title title="Success lights large" theme="light" level={4} /> + <DxcStatusLight label="StatusLight" mode="success" size="large" /> + </ExampleContainer> + <ExampleContainer> + <Title title="Warning light small" theme="light" level={4} /> + <DxcStatusLight label="StatusLight" mode="warning" size="small" /> + </ExampleContainer> + <ExampleContainer> + <Title title="Warning light medium" theme="light" level={4} /> + <DxcStatusLight label="StatusLight" mode="warning" /> + </ExampleContainer> + <ExampleContainer> + <Title title="Warning light large" theme="light" level={4} /> + <DxcStatusLight label="StatusLight" mode="warning" size="large" /> + </ExampleContainer> + <ExampleContainer> + <Title title="Error light small" theme="light" level={4} /> + <DxcStatusLight label="StatusLight" mode="error" size="small" /> + </ExampleContainer> + <ExampleContainer> + <Title title="Error lights medium" theme="light" level={4} /> + <DxcStatusLight label="StatusLight" mode="error" /> + </ExampleContainer> + <ExampleContainer> + <Title title="Error lights large" theme="light" level={4} /> + <DxcStatusLight label="StatusLight" mode="error" size="large" /> + </ExampleContainer> + </> +); diff --git a/lib/src/status-light/StatusLight.test.js b/lib/src/status-light/StatusLight.test.js new file mode 100644 index 000000000..a366e8cab --- /dev/null +++ b/lib/src/status-light/StatusLight.test.js @@ -0,0 +1,18 @@ +import React from "react"; +import { render } from "@testing-library/react"; +import DxcStatusLight from "./StatusLight.tsx"; + +describe("StatusLight component tests", () => { + test("StatusLight renders with correct label", () => { + const { getByText } = render(<DxcStatusLight label="Status Light Test"></DxcStatusLight>); + expect(getByText("Status Light Test")).toBeTruthy(); + }); + + test("StatusLight applies accessibility attributes", () => { + const { getByTestId } = render(<DxcStatusLight label="Status Light Test" />); + const statusLightContainer = getByTestId("status_light-container"); + const statusDot = getByTestId("status-dot"); + expect(statusLightContainer.getAttribute("aria-label")).toBe("default: Status Light Test"); + expect(statusDot.getAttribute("aria-hidden")).toBe("true"); + }); +}); diff --git a/lib/src/status-light/StatusLight.tsx b/lib/src/status-light/StatusLight.tsx new file mode 100644 index 000000000..fe1616f10 --- /dev/null +++ b/lib/src/status-light/StatusLight.tsx @@ -0,0 +1,72 @@ +import React from "react"; +import styled from "styled-components"; +import StatusLightPropsType from "./types"; +import CoreTokens from "../common/coreTokens"; + +const DxcStatusLight = ({ mode = "default", label, size = "medium" }: StatusLightPropsType): JSX.Element => { + return ( + <StatusLightContainer size={size} aria-label={`${mode}: ${label}`} data-testid="status_light-container"> + <StatusDot mode={mode} size={size} aria-hidden="true" data-testid="status-dot" /> + <StatusLabel mode={mode} size={size}> + {label} + </StatusLabel> + </StatusLightContainer> + ); +}; + +const StatusLightContainer = styled.div<{ size: StatusLightPropsType["size"] }>` + display: inline-flex; + align-items: center; + gap: ${CoreTokens.spacing_8}; +`; + +const StatusDot = styled.div<{ + mode: StatusLightPropsType["mode"]; + size: 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}; + 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}; +`; + +const StatusLabel = styled.span<{ + mode: StatusLightPropsType["mode"]; + size: 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; + overflow: hidden; + white-space: nowrap; +`; + +export default DxcStatusLight; diff --git a/lib/src/status-light/types.ts b/lib/src/status-light/types.ts new file mode 100644 index 000000000..be41ce9b3 --- /dev/null +++ b/lib/src/status-light/types.ts @@ -0,0 +1,19 @@ +type Mode = "default" | "info" | "success" | "warning" | "error"; +type Size = "small" | "medium" | "large"; + +type Props = { + /** + * It will define the color of the light based on its semantic meaning. + */ + mode?: Mode; + /** + * An auxiliar text that will add some context to the status. + */ + label: string; + /** + * Size of the component. Should be defined based on its importance and/or available space. + */ + size?: Size; +}; + +export default Props; diff --git a/website/pages/components/status-light/index.tsx b/website/pages/components/status-light/index.tsx new file mode 100644 index 000000000..23a248858 --- /dev/null +++ b/website/pages/components/status-light/index.tsx @@ -0,0 +1,21 @@ +import Head from "next/head"; +import type { ReactElement } from "react"; +import StatusLightCodePage from "../../../screens/components/status-light/code/StatusLightCodePage"; +import StatusLightPageLayout from "../../../screens/components/status-light/StatusLightPageLayout"; + +const Index = () => { + return ( + <> + <Head> + <title>Status Light — Halstack Design System + + + + ); +}; + +Index.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; + +export default Index; diff --git a/website/screens/common/componentList.js b/website/screens/common/componentList.js index e3d1e19ad..5f1ca1a18 100644 --- a/website/screens/common/componentList.js +++ b/website/screens/common/componentList.js @@ -51,6 +51,7 @@ exports.componentsList = [ { label: "Sidenav", path: "/components/sidenav", status: "Ready" }, { label: "Slider", path: "/components/slider", status: "Ready" }, { label: "Spinner", path: "/components/spinner", status: "Ready" }, + { label: "Status Light", path: "/components/status-light", status: "Experimental" }, { label: "Switch", path: "/components/switch", status: "Ready" }, { label: "Table", path: "/components/table", status: "Ready" }, { label: "Tabs", path: "/components/tabs", status: "Ready" }, diff --git a/website/screens/components/status-light/StatusLightPageLayout.tsx b/website/screens/components/status-light/StatusLightPageLayout.tsx new file mode 100644 index 000000000..b5fdf55c0 --- /dev/null +++ b/website/screens/components/status-light/StatusLightPageLayout.tsx @@ -0,0 +1,27 @@ +import { DxcParagraph, DxcFlex } from "@dxc-technology/halstack-react"; +import PageHeading from "@/common/PageHeading"; +import TabsPageHeading from "@/common/TabsPageLayout"; +import ComponentHeading from "@/common/ComponentHeading"; + +const StatusLightPageHeading = ({ children }: { children: React.ReactNode }) => { + const tabs = [ + { label: "Code", path: "/components/status-light" }, + ]; + + return ( + + + + + + Status Lights, as semantic elements, allow the user to display the completion status of tasks, processes and more. + + + + + {children} + + ); +}; + +export default StatusLightPageHeading; diff --git a/website/screens/components/status-light/code/StatusLightCodePage.tsx b/website/screens/components/status-light/code/StatusLightCodePage.tsx new file mode 100644 index 000000000..4a70bfb26 --- /dev/null +++ b/website/screens/components/status-light/code/StatusLightCodePage.tsx @@ -0,0 +1,86 @@ +import { DxcFlex, DxcTable } from "@dxc-technology/halstack-react"; +import QuickNavContainer from "@/common/QuickNavContainer"; +import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; +import DocFooter from "@/common/DocFooter"; +import TableCode from "@/common/TableCode"; + +const sections = [ + { + title: "Props", + content: ( + + + + Name + Type + Description + Default + + + + + mode + + + 'default' | 'info' | 'success' | 'warning' | 'error' + + + + It will define the color of the light based on its semantic + meaning. + + + 'default' + + + + label + + string + + + An auxiliar text that will add some context to the status. + + - + + + size + + 'small' | 'medium' | 'large' + + + Size of the component. Should be defined based on its importance + and/or available space. + + 'medium' + + + + ), + }, + { + title: "Examples", + subSections: [ + { + title: "Basic Usage", + content:

Examples are not available yet, they will be added soon.

+ }, + ], + }, +]; + +const StatusLightCodePage = () => { + return ( + + + + + + + ); +}; + +export default StatusLightCodePage;