From bfcbc810fe2ca040241eee9e7aa2f1d6ff377257 Mon Sep 17 00:00:00 2001 From: Mil4n0r Date: Mon, 5 Feb 2024 16:42:11 +0100 Subject: [PATCH 01/10] Finished first skeleton of Status Light Component --- lib/src/common/variables.ts | 11 + lib/src/status-light/StatusLight.stories.tsx | 75 ++++ lib/src/status-light/StatusLight.test.js | 32 ++ lib/src/status-light/StatusLight.tsx | 78 ++++ lib/src/status-light/types.ts | 19 + .../pages/components/status-light/index.tsx | 21 + .../status-light/specifications.tsx | 21 + .../pages/components/status-light/usage.tsx | 21 + .../status-light/StatusLightPageLayout.tsx | 29 ++ .../status-light/code/StatusLightCodePage.tsx | 85 ++++ .../status-light/code/examples/basicUsage.ts | 16 + .../specs/StatusLightSpecsPage.tsx | 424 ++++++++++++++++++ .../specs/images/status-light_anatomy.png | Bin 0 -> 10972 bytes .../specs/images/status-light_specs.png | Bin 0 -> 14296 bytes .../images/status-light_states_action.png | Bin 0 -> 12853 bytes .../images/status-light_states_container.png | Bin 0 -> 8287 bytes .../usage/StatusLightUsagePage.tsx | 42 ++ 17 files changed, 874 insertions(+) create mode 100644 lib/src/status-light/StatusLight.stories.tsx create mode 100644 lib/src/status-light/StatusLight.test.js create mode 100644 lib/src/status-light/StatusLight.tsx create mode 100644 lib/src/status-light/types.ts create mode 100644 website/pages/components/status-light/index.tsx create mode 100644 website/pages/components/status-light/specifications.tsx create mode 100644 website/pages/components/status-light/usage.tsx create mode 100644 website/screens/components/status-light/StatusLightPageLayout.tsx create mode 100644 website/screens/components/status-light/code/StatusLightCodePage.tsx create mode 100644 website/screens/components/status-light/code/examples/basicUsage.ts create mode 100644 website/screens/components/status-light/specs/StatusLightSpecsPage.tsx create mode 100644 website/screens/components/status-light/specs/images/status-light_anatomy.png create mode 100644 website/screens/components/status-light/specs/images/status-light_specs.png create mode 100644 website/screens/components/status-light/specs/images/status-light_states_action.png create mode 100644 website/screens/components/status-light/specs/images/status-light_states_container.png create mode 100644 website/screens/components/status-light/usage/StatusLightUsagePage.tsx diff --git a/lib/src/common/variables.ts b/lib/src/common/variables.ts index a669f2f8a..ba986d31b 100644 --- a/lib/src/common/variables.ts +++ b/lib/src/common/variables.ts @@ -809,6 +809,17 @@ export const componentTokens = { overlayProgressValueFontColor: CoreTokens.color_white, overlayProgressValueTextAlign: "center", }, + statusLight: { + fontFamily: CoreTokens.type_sans, + fontStyle: CoreTokens.type_normal, + fontWeight: CoreTokens.type_semibold, + focusColor: CoreTokens.color_blue_600, + defaultColor: CoreTokens.color_grey_700, + errorColor: CoreTokens.color_red_700, + infoColor: CoreTokens.color_blue_700, + successColor: CoreTokens.color_green_700, + warningColor: CoreTokens.color_orange_700 + }, switch: { checkedTrackBackgroundColor: CoreTokens.color_purple_700, checkedThumbBackgroundColor: CoreTokens.color_white, diff --git a/lib/src/status-light/StatusLight.stories.tsx b/lib/src/status-light/StatusLight.stories.tsx new file mode 100644 index 000000000..04acb9af3 --- /dev/null +++ b/lib/src/status-light/StatusLight.stories.tsx @@ -0,0 +1,75 @@ +import React from "react"; +import { userEvent, within } from "@storybook/testing-library"; +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..5d41cabd3 --- /dev/null +++ b/lib/src/status-light/StatusLight.test.js @@ -0,0 +1,32 @@ +import React from "react"; +import { render, fireEvent } 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 renders with correct label before", () => { + const { getByText } = render(<DxcStatusLight label="status-light-test" labelPosition="before"></DxcStatusLight>); + expect(getByText("status-light-test")).toBeTruthy(); + }); + + test("StatusLight renders with correct icon", () => { + const { getByRole } = render(<DxcStatusLight label="status-light-test" icon="/test-icon.jpg"></DxcStatusLight>); + expect(getByRole("img").getAttribute("src")).toBe("/test-icon.jpg"); + }); + + test("StatusLight renders with link href", () => { + const { getByRole } = render(<DxcStatusLight label="status-light-test" linkHref="/test/page"></DxcStatusLight>); + expect(getByRole("link").getAttribute("href")).toBe("/test/page"); + }); + + test("Call correct function on click", () => { + const onClick = jest.fn(); + const { getByText } = render(<DxcStatusLight label="status-light-test" onClick={onClick}></DxcStatusLight>); + fireEvent.click(getByText("status-light-test")); + expect(onClick).toHaveBeenCalled(); + }); +}); diff --git a/lib/src/status-light/StatusLight.tsx b/lib/src/status-light/StatusLight.tsx new file mode 100644 index 000000000..22267fc44 --- /dev/null +++ b/lib/src/status-light/StatusLight.tsx @@ -0,0 +1,78 @@ +import React, { useState } from "react"; +import styled, { ThemeProvider } from "styled-components"; +import { spaces } from "../common/variables"; +import useTheme from "../useTheme"; +import { getMargin } from "../common/utils"; +import DxcBox from "../box/Box"; +import StatusLightPropsType from "./types"; + +const DxcStatusLight = ({ mode = "default", label, size = "medium" }: StatusLightPropsType): JSX.Element => { + const colorsTheme = useTheme(); + const [isHovered, changeIsHovered] = useState(false); + + return ( + <ThemeProvider theme={colorsTheme.statusLight}> + <StatusLightWrapper> + <StatusDot mode={mode} size={size} /> + <StatusLabel mode={mode} size={size}> + {label} + </StatusLabel> + </StatusLightWrapper> + </ThemeProvider> + ); +}; + +const sizes = { + small: "12px", + medium: "14px", + large: "16px", +}; + +const calculateWidth = (margin, size) => + size === "fillParent" + ? `calc(${sizes[size]} - ${getMargin(margin, "left")} - ${getMargin(margin, "right")})` + : sizes[size]; + +// Define the styled components +const StatusLightWrapper = styled.div` + display: flex; + align-items: center; +`; + +const StatusDot = styled.div<{ + mode: StatusLightPropsType["mode"]; + size: StatusLightPropsType["size"]; +}>` + width: ${({ size }) => sizes[size]}; + height: ${({ size }) => sizes[size]}; + border-radius: 50%; + background-color: ${(props) => + (props.mode === "default" && props.theme.defaultColor) || + (props.mode === "error" && props.theme.errorColor) || + (props.mode === "info" && props.theme.infoColor) || + (props.mode === "success" && props.theme.successColor) || + (props.mode === "warning" && props.theme.warningColor)}; + margin-right: 8px; +`; + +const StatusLabel = styled.span<{ + mode: StatusLightPropsType["mode"]; + size: StatusLightPropsType["size"]; +}>` + font-size: ${({ size }) => sizes[size]}; + font-family: ${(props) => props.theme.fontFamily}; + font-style: ${(props) => props.theme.fontStyle}; + font-weight: ${(props) => props.theme.fontWeight}; + color: ${(props) => + (props.mode === "default" && props.theme.defaultColor) || + (props.mode === "error" && props.theme.errorColor) || + (props.mode === "info" && props.theme.infoColor) || + (props.mode === "success" && props.theme.successColor) || + (props.mode === "warning" && props.theme.warningColor)}; + 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..d8b7a808c --- /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; + /** + * A short 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/pages/components/status-light/specifications.tsx b/website/pages/components/status-light/specifications.tsx new file mode 100644 index 000000000..022d6c122 --- /dev/null +++ b/website/pages/components/status-light/specifications.tsx @@ -0,0 +1,21 @@ +import Head from "next/head"; +import type { ReactElement } from "react"; +import StatusLightSpecsPage from "../../../screens/components/status-light/specs/StatusLightSpecsPage"; +import StatusLightPageLayout from "../../../screens/components/status-light/StatusLightPageLayout"; + +const Specifications = () => { + return ( + <> + + Status Light Specs — Halstack Design System + + + + ); +}; + +Specifications.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; + +export default Specifications; diff --git a/website/pages/components/status-light/usage.tsx b/website/pages/components/status-light/usage.tsx new file mode 100644 index 000000000..37079fa6b --- /dev/null +++ b/website/pages/components/status-light/usage.tsx @@ -0,0 +1,21 @@ +import Head from "next/head"; +import type { ReactElement } from "react"; +import StatusLightUsagePage from "../../../screens/components/status-light/usage/StatusLightUsagePage"; +import StatusLightPageLayout from "../../../screens/components/status-light/StatusLightPageLayout"; + +const Usage = () => { + return ( + <> + + Status Light Usage — Halstack Design System + + + + ); +}; + +Usage.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; + +export default Usage; diff --git a/website/screens/components/status-light/StatusLightPageLayout.tsx b/website/screens/components/status-light/StatusLightPageLayout.tsx new file mode 100644 index 000000000..0ffaa8e80 --- /dev/null +++ b/website/screens/components/status-light/StatusLightPageLayout.tsx @@ -0,0 +1,29 @@ +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" }, + { label: "Usage", path: "/components/status-light/usage" }, + { label: "Specifications", path: "/components/status-light/specifications" }, + ]; + + 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..a8dcc3dba --- /dev/null +++ b/website/screens/components/status-light/code/StatusLightCodePage.tsx @@ -0,0 +1,85 @@ +import { 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 Code from "@/common/Code"; +// import basicUsage from "./examples/basicUsage"; +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 + + + + A short 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. + + - + + + + ), + }, + // { + // title: "Examples", + // subSections: [ + // { + // title: "Basic usage", + // content: , + // } + // ], + // }, +]; + +const StatusLightCodePage = () => { + return ( + + + + + + + ); +}; + +export default StatusLightCodePage; diff --git a/website/screens/components/status-light/code/examples/basicUsage.ts b/website/screens/components/status-light/code/examples/basicUsage.ts new file mode 100644 index 000000000..dd9ad8186 --- /dev/null +++ b/website/screens/components/status-light/code/examples/basicUsage.ts @@ -0,0 +1,16 @@ +// import { DxcStatusLight, DxcInset } from "@dxc-technology/halstack-react"; + +// const code = `() => { +// return ( +// +// +// +// ); +// }`; + +// const scope = { +// DxcStatusLight, +// DxcInset, +// }; + +// export default { code, scope }; diff --git a/website/screens/components/status-light/specs/StatusLightSpecsPage.tsx b/website/screens/components/status-light/specs/StatusLightSpecsPage.tsx new file mode 100644 index 000000000..b7c100e4c --- /dev/null +++ b/website/screens/components/status-light/specs/StatusLightSpecsPage.tsx @@ -0,0 +1,424 @@ +import { + DxcBulletedList, + DxcFlex, + DxcTable, + DxcParagraph, +} from "@dxc-technology/halstack-react"; +import QuickNavContainer from "@/common/QuickNavContainer"; +import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; +import DocFooter from "@/common/DocFooter"; +import Figure from "@/common/Figure"; +import Image from "@/common/Image"; +import Code from "@/common/Code"; +import specsImage from "./images/status-light_specs.png"; +import statesImage from "./images/status-light_states_container.png"; +import prefixSuffixStatesImage from "./images/status-light_states_action.png"; +import anatomyImage from "./images/status-light_anatomy.png"; + +const sections = [ + { + title: "Specifications", + content: ( +
+ Status Light design specifications +
+ ), + }, + // { + // title: "States", + // content: ( + // <> + //
+ // Tag states + //
+ // + // ???? + // + // + // ), + // }, + // { + // title: "Anatomy", + // content: ( + // <> + // {/* Status Light anatomy + // + // Container + // + // Prefix (Optional) + // + // Label + // + // Suffix (Optional) + // + // */} + // + // ), + // }, + // { + // title: "Design tokens", + // subSections: [ + // { + // title: "Color", + // content: ( + // + // + // + // Component token + // Element + // Core token + // Value + // + // + // + // + // + // activeIconColor + // + // Container:active + // + // color-black + // + // #000000 + // + // + // + // backgroundColor + // + // Container + // + // color-grey-200 + // + // #e6e6e6 + // + // + // + // borderColor + // + // Container:border + // + // color-transparent + // + // transparent + // + // + // + // disabledBackgroundColor + // + // Container:disabled + // + // color-grey-100 + // + // #f2f2f2 + // + // + // + // disabledFontcolor + // + // Label:disabled + // + // color-grey-500 + // + // #999999 + // + // + // + // disabledIconColor + // + // Icon:disabled + // + // color-grey-500 + // + // #999999 + // + // + // + // focusColor + // + // Focus outline + // + // color-blue-600 + // + // #0095ff + // + // + // + // fontColor + // + // Label + // + // color-black + // + // #000000 + // + // + // + // hoverIconColor + // + // Container:hover + // + // color-grey-900 + // + // #333333 + // + // + // + // iconColor + // + // Icon + // + // color-grey-800 + // + // #4d4d4d + // + // + // + // ), + // }, + // { + // title: "Typography", + // content: ( + // + // + // + // Component token + // Element + // Core token + // Value + // + // + // + // + // + // fontFamily + // + // Label + // + // font-family-sans + // + // 'Open Sans', sans-serif + // + // + // + // fontSize + // + // Label + // + // font-scale-03 + // + // 1rem / 16px + // + // + // + // fontStyle + // + // Label + // + // font-style-normal + // + // normal + // + // + // + // fontWeight + // + // Label + // + // font-weight-regular + // + // 400 + // + // + // + // ), + // }, + // { + // title: "Spacing", + // content: ( + // + // + // + // Component token + // Element + // Core token + // Value + // + // + // + // + // + // contentPaddingLeft + // + // Container + // + // spacing-16 + // + // 1rem / 16px + // + // + // + // contentPaddingRight + // + // Container + // + // spacing-16 + // + // 1rem / 16px + // + // + // + // contentPaddingTop + // + // Container + // + // spacing-0 + // + // 0 + // + // + // + // contentPaddingBottom + // + // Container + // + // spacing-0 + // + // 0 + // + // + // + // iconSpacing + // + // Icon + // + // spacing-8 + // + // 0.5rem / 8px + // + // + // + // ), + // }, + // { + // title: "Border", + // content: ( + // + // + // + // Property + // Element + // Core token + // Value + // + // + // + // + // + // border-width + // + // Status Light container + // + // border-width-0 + // + // 0 + // + // + // + // border-style + // + // Status Light container + // + // border-style-none + // + // none + // + // + // + // border-radius + // + // Status Light container + // + // border-radius-full + // + // 9999px + // + // + // + // border-width + // + // Focus border + // + // border-width-2 + // + // 2px + // + // + // + // border-style + // + // Focus border + // + // border-style-solid + // + // solid + // + // + // + // border-radius + // + // Focus border + // + // border-radius-medium + // + // 0.25rem / 4px + // + // + // + // ), + // }, + // { + // title: "Size", + // content: ( + // + // + // + // Component token + // Element + // Core token + // Value + // + // + // + // + // + // iconSize + // + // Prefix/Suffix + // + // size-icon-large + // + // 24x24px + // + // + // + // ), + // }, + // ], + // }, +]; + +const StatusLightSpecsPage = () => { + return ( + + + + + + + ); +}; + +export default StatusLightSpecsPage; diff --git a/website/screens/components/status-light/specs/images/status-light_anatomy.png b/website/screens/components/status-light/specs/images/status-light_anatomy.png new file mode 100644 index 0000000000000000000000000000000000000000..33ababa249d0a388db5a71661374f19b630ec91c GIT binary patch literal 10972 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 G=YIg(W9+~H literal 0 HcmV?d00001 diff --git a/website/screens/components/status-light/specs/images/status-light_states_action.png b/website/screens/components/status-light/specs/images/status-light_states_action.png new file mode 100644 index 0000000000000000000000000000000000000000..6e37083ab47c738fb53800bf9b33c64e4ec7f8ee GIT binary patch literal 12853 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 literal 0 HcmV?d00001 diff --git a/website/screens/components/status-light/specs/images/status-light_states_container.png b/website/screens/components/status-light/specs/images/status-light_states_container.png new file mode 100644 index 0000000000000000000000000000000000000000..961986709c6e322b8fde877b5e90abdc2800825d GIT binary patch literal 8287 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 YUoF_+pqa8`pcgVWv^Y_C+~xZJ0QU$9#{d8T literal 0 HcmV?d00001 diff --git a/website/screens/components/status-light/usage/StatusLightUsagePage.tsx b/website/screens/components/status-light/usage/StatusLightUsagePage.tsx new file mode 100644 index 000000000..f0dd278ed --- /dev/null +++ b/website/screens/components/status-light/usage/StatusLightUsagePage.tsx @@ -0,0 +1,42 @@ +import { DxcBulletedList, DxcFlex } from "@dxc-technology/halstack-react"; +import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; +import QuickNavContainer from "@/common/QuickNavContainer"; +import DocFooter from "@/common/DocFooter"; + +const sections = [ + { + title: "Usage", + content: ( + + + Use the Status Light component to clearly communicate statuses of tasks, processes or any other relevant indicators. + + + Make sure the label used is no longer than 3 words and not too detailed. Keep it simple. + + + If the semantic colors are not suitable for your use case, consider using a Badge instead. + + + Do not use multiple Status Lights within the same view. It could be confusing as they are not interactive items. + + + ), + }, +]; + +const StatusLightUsagePage = () => { + return ( + + + + + + + ); +}; + +export default StatusLightUsagePage; From 625b6b7618279000996af98791aeca4a566fc14c Mon Sep 17 00:00:00 2001 From: Mil4n0r Date: Mon, 5 Feb 2024 17:03:53 +0100 Subject: [PATCH 02/10] Cleaned StatusLight code --- lib/src/status-light/StatusLight.tsx | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/src/status-light/StatusLight.tsx b/lib/src/status-light/StatusLight.tsx index 22267fc44..21c91485a 100644 --- a/lib/src/status-light/StatusLight.tsx +++ b/lib/src/status-light/StatusLight.tsx @@ -1,23 +1,19 @@ -import React, { useState } from "react"; +import React from "react"; import styled, { ThemeProvider } from "styled-components"; -import { spaces } from "../common/variables"; import useTheme from "../useTheme"; -import { getMargin } from "../common/utils"; -import DxcBox from "../box/Box"; import StatusLightPropsType from "./types"; const DxcStatusLight = ({ mode = "default", label, size = "medium" }: StatusLightPropsType): JSX.Element => { const colorsTheme = useTheme(); - const [isHovered, changeIsHovered] = useState(false); return ( - + {label} - + ); }; @@ -28,15 +24,17 @@ const sizes = { large: "16px", }; -const calculateWidth = (margin, size) => - size === "fillParent" - ? `calc(${sizes[size]} - ${getMargin(margin, "left")} - ${getMargin(margin, "right")})` - : sizes[size]; +const paddings = { + small: "2px", + medium: "2.5px", + large: "4.5px", +}; // Define the styled components -const StatusLightWrapper = styled.div` +const StatusLightContainer = styled.div<{size: StatusLightPropsType["size"] }>` display: flex; align-items: center; + padding: ${({ size }) => paddings[size]} 0px; `; const StatusDot = styled.div<{ From c474f6810ba823ac359f0d34b3bca56a0dc424dc Mon Sep 17 00:00:00 2001 From: Mil4n0r Date: Tue, 6 Feb 2024 12:42:02 +0100 Subject: [PATCH 03/10] First working version of Status Light --- lib/src/common/variables.ts | 1 - lib/src/main.ts | 2 + lib/src/status-light/StatusLight.stories.tsx | 1 - lib/src/status-light/StatusLight.test.js | 32 +- lib/src/status-light/StatusLight.tsx | 23 +- .../status-light/code/StatusLightCodePage.tsx | 2 +- .../status-light/code/examples/basicUsage.ts | 1 + .../specs/StatusLightSpecsPage.tsx | 367 +----------------- .../specs/images/status-light_anatomy.png | Bin 10972 -> 0 bytes .../specs/images/status-light_specs.png | Bin 14296 -> 0 bytes .../images/status-light_states_action.png | Bin 12853 -> 0 bytes .../images/status-light_states_container.png | Bin 8287 -> 0 bytes 12 files changed, 31 insertions(+), 398 deletions(-) delete mode 100644 website/screens/components/status-light/specs/images/status-light_anatomy.png delete mode 100644 website/screens/components/status-light/specs/images/status-light_specs.png delete mode 100644 website/screens/components/status-light/specs/images/status-light_states_action.png delete mode 100644 website/screens/components/status-light/specs/images/status-light_states_container.png diff --git a/lib/src/common/variables.ts b/lib/src/common/variables.ts index ba986d31b..33cf083ad 100644 --- a/lib/src/common/variables.ts +++ b/lib/src/common/variables.ts @@ -813,7 +813,6 @@ export const componentTokens = { fontFamily: CoreTokens.type_sans, fontStyle: CoreTokens.type_normal, fontWeight: CoreTokens.type_semibold, - focusColor: CoreTokens.color_blue_600, defaultColor: CoreTokens.color_grey_700, errorColor: CoreTokens.color_red_700, infoColor: CoreTokens.color_blue_700, 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 index 04acb9af3..c4cd3720c 100644 --- a/lib/src/status-light/StatusLight.stories.tsx +++ b/lib/src/status-light/StatusLight.stories.tsx @@ -1,5 +1,4 @@ import React from "react"; -import { userEvent, within } from "@storybook/testing-library"; import Title from "../../.storybook/components/Title"; import ExampleContainer from "../../.storybook/components/ExampleContainer"; import DxcStatusLight from "./StatusLight"; diff --git a/lib/src/status-light/StatusLight.test.js b/lib/src/status-light/StatusLight.test.js index 5d41cabd3..a366e8cab 100644 --- a/lib/src/status-light/StatusLight.test.js +++ b/lib/src/status-light/StatusLight.test.js @@ -1,32 +1,18 @@ import React from "react"; -import { render, fireEvent } from "@testing-library/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(); - expect(getByText("status-light-test")).toBeTruthy(); + const { getByText } = render(); + expect(getByText("Status Light Test")).toBeTruthy(); }); - test("StatusLight renders with correct label before", () => { - const { getByText } = render(); - expect(getByText("status-light-test")).toBeTruthy(); - }); - - test("StatusLight renders with correct icon", () => { - const { getByRole } = render(); - expect(getByRole("img").getAttribute("src")).toBe("/test-icon.jpg"); - }); - - test("StatusLight renders with link href", () => { - const { getByRole } = render(); - expect(getByRole("link").getAttribute("href")).toBe("/test/page"); - }); - - test("Call correct function on click", () => { - const onClick = jest.fn(); - const { getByText } = render(); - fireEvent.click(getByText("status-light-test")); - expect(onClick).toHaveBeenCalled(); + test("StatusLight applies accessibility attributes", () => { + const { getByTestId } = render(); + 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 index 21c91485a..b47987da9 100644 --- a/lib/src/status-light/StatusLight.tsx +++ b/lib/src/status-light/StatusLight.tsx @@ -8,8 +8,8 @@ const DxcStatusLight = ({ mode = "default", label, size = "medium" }: StatusLigh return ( - - + +