diff --git a/apps/website/screens/common/themes/advanced-theme.json b/apps/website/screens/common/themes/advanced-theme.json
index 0a2cbdc4aa..2ec0b6c056 100644
--- a/apps/website/screens/common/themes/advanced-theme.json
+++ b/apps/website/screens/common/themes/advanced-theme.json
@@ -403,15 +403,8 @@
"dialog": {
"overlayColor": "#000000b3",
"backgroundColor": "#ffffff",
- "closeIconSize": "24px",
- "closeIconTopPosition": "20px",
- "closeIconRightPosition": "20px",
"closeIconBackgroundColor": "transparent",
- "closeIconBorderColor": "none",
"closeIconColor": "#000000",
- "closeIconBorderThickness": "0px",
- "closeIconBorderStyle": "solid",
- "closeIconBorderRadius": "2px",
"boxShadowOffsetX": "0px",
"boxShadowOffsetY": "1px",
"boxShadowBlur": "3px",
@@ -546,15 +539,15 @@
},
"header": {
"backgroundColor": "#ffffff",
- "hamburguerFocusColor": "#0095ff",
- "hamburguerFontFamily": "Open Sans, sans-serif",
- "hamburguerFontStyle": "normal",
- "hamburguerFontColor": "#000000",
- "hamburguerFontSize": "10px",
- "hamburguerFontWeight": "600",
- "hamburguerTextTransform": "uppercase",
- "hamburguerIconColor": "#000000",
- "hamburguerHoverColor": "#e6e6e6",
+ "hamburgerFocusColor": "#0095ff",
+ "hamburgerFontFamily": "Open Sans, sans-serif",
+ "hamburgerFontStyle": "normal",
+ "hamburgerFontColor": "#000000",
+ "hamburgerFontSize": "10px",
+ "hamburgerFontWeight": "600",
+ "hamburgerTextTransform": "uppercase",
+ "hamburgerIconColor": "#000000",
+ "hamburgerHoverColor": "#e6e6e6",
"logo": "",
"logoResponsive": "",
"logoHeight": "40px",
diff --git a/apps/website/screens/common/themes/opinionated-theme.json b/apps/website/screens/common/themes/opinionated-theme.json
index 092f4ebb58..050215d6ad 100644
--- a/apps/website/screens/common/themes/opinionated-theme.json
+++ b/apps/website/screens/common/themes/opinionated-theme.json
@@ -58,7 +58,7 @@
"accentColor": "#000000",
"fontColor": "#000000",
"menuBaseColor": "#ffffff",
- "hamburguerColor": "#000000",
+ "hamburgerColor": "#000000",
"logo": "/dxc_header_logo.svg",
"logoResponsive": "/dxc_header_logo.svg",
"contentColor": "#000000",
diff --git a/apps/website/screens/components/accordion/code/AccordionCodePage.tsx b/apps/website/screens/components/accordion/code/AccordionCodePage.tsx
index 44e31be748..38a210a296 100644
--- a/apps/website/screens/components/accordion/code/AccordionCodePage.tsx
+++ b/apps/website/screens/components/accordion/code/AccordionCodePage.tsx
@@ -45,7 +45,9 @@ const sections = [
boolean
Initial state of the panel, only when it is uncontrolled.
- -
+
+ false
+
isExpanded
diff --git a/apps/website/screens/components/checkbox/code/CheckboxCodePage.tsx b/apps/website/screens/components/checkbox/code/CheckboxCodePage.tsx
index 1aa21a0573..71d37db836 100644
--- a/apps/website/screens/components/checkbox/code/CheckboxCodePage.tsx
+++ b/apps/website/screens/components/checkbox/code/CheckboxCodePage.tsx
@@ -42,7 +42,7 @@ const sections = [
managed internally by the component.
- false
+ -
diff --git a/apps/website/screens/components/dialog/specs/DialogSpecsPage.tsx b/apps/website/screens/components/dialog/specs/DialogSpecsPage.tsx
index 07d9e50616..0ea5c60f56 100644
--- a/apps/website/screens/components/dialog/specs/DialogSpecsPage.tsx
+++ b/apps/website/screens/components/dialog/specs/DialogSpecsPage.tsx
@@ -98,14 +98,6 @@ const sections = [
transparent
-
-
- closeIconBorderColor
-
- Icon close
- -
- -
-
),
@@ -161,48 +153,6 @@ const sections = [
title: "Border",
content: (
<>
-
-
-
- Component token
- Element
- Core token
- Value
-
-
-
-
-
- closeIconBorderThickness
-
- Icon close
-
- border-width-0
-
- 0px
-
-
-
- closeIconBorderStyle
-
- Icon close
-
- border-style-solid
-
- solid
-
-
-
- closeIconBorderRadius
-
- Icon close
-
- border-radius-small
-
- 0.125rem / 2px
-
-
-
diff --git a/apps/website/screens/components/header/specs/HeaderSpecsPage.tsx b/apps/website/screens/components/header/specs/HeaderSpecsPage.tsx
index 9a9e5b8047..9414807ee6 100644
--- a/apps/website/screens/components/header/specs/HeaderSpecsPage.tsx
+++ b/apps/website/screens/components/header/specs/HeaderSpecsPage.tsx
@@ -80,7 +80,7 @@ const sections = [
- hamburguerHoverColor
+ hamburgerHoverColor
Menu:hover
@@ -90,7 +90,7 @@ const sections = [
- hamburguerFocusColor
+ hamburgerFocusColor
Menu:focus
@@ -100,7 +100,7 @@ const sections = [
- hamburguerFontColor
+ hamburgerFontColor
Menu label
@@ -110,7 +110,7 @@ const sections = [
- hamburguerIconColor
+ hamburgerIconColor
Menu icon
@@ -157,7 +157,7 @@ const sections = [
- hamburguerFontFamily
+ hamburgerFontFamily
Menu label
@@ -167,7 +167,7 @@ const sections = [
- hamburguerFontStyle
+ hamburgerFontStyle
Menu label
@@ -177,7 +177,7 @@ const sections = [
- hamburguerFontSize
+ hamburgerFontSize
Menu label
@@ -187,7 +187,7 @@ const sections = [
- hamburguerFontWeight
+ hamburgerFontWeight
Menu label
diff --git a/apps/website/screens/components/progress-bar/code/ProgressBarCodePage.tsx b/apps/website/screens/components/progress-bar/code/ProgressBarCodePage.tsx
index 6d2350de2e..4cc180b298 100644
--- a/apps/website/screens/components/progress-bar/code/ProgressBarCodePage.tsx
+++ b/apps/website/screens/components/progress-bar/code/ProgressBarCodePage.tsx
@@ -63,7 +63,7 @@ const sections = [
boolean
- If true, the value is displayed above the progress bar.
+ If true, the determined value is displayed above the progress bar.
false
diff --git a/apps/website/screens/components/progress-bar/code/examples/overlay.ts b/apps/website/screens/components/progress-bar/code/examples/overlay.ts
index 29c7108e69..0cdd0be6f2 100644
--- a/apps/website/screens/components/progress-bar/code/examples/overlay.ts
+++ b/apps/website/screens/components/progress-bar/code/examples/overlay.ts
@@ -25,11 +25,10 @@ const code = `() => {
label="Show Progress Bar for 3 seconds"
onClick={showModal}
/>
-
{isVisible && (
)}
diff --git a/apps/website/screens/components/switch/code/SwitchCodePage.tsx b/apps/website/screens/components/switch/code/SwitchCodePage.tsx
index bcb068d27c..170c598243 100644
--- a/apps/website/screens/components/switch/code/SwitchCodePage.tsx
+++ b/apps/website/screens/components/switch/code/SwitchCodePage.tsx
@@ -28,7 +28,9 @@ const sections = [
boolean
Initial state of the switch, only when it is uncontrolled.
- -
+
+ false
+
checked
@@ -39,9 +41,7 @@ const sections = [
If true, the component is checked. If undefined, the component will be uncontrolled and the checked
attribute will be managed internally by the component.
-
- false
-
+ -
value
diff --git a/apps/website/screens/components/toast/code/ToastCodePage.tsx b/apps/website/screens/components/toast/code/ToastCodePage.tsx
index b9c45f2a70..3ac8256f14 100644
--- a/apps/website/screens/components/toast/code/ToastCodePage.tsx
+++ b/apps/website/screens/components/toast/code/ToastCodePage.tsx
@@ -49,6 +49,14 @@ const sections = [
3000
+
+ children
+
+ ReactNode
+
+ Tree of components from which the useToast hook can be triggered.
+ -
+
),
diff --git a/apps/website/screens/components/wizard/code/WizardCodePage.tsx b/apps/website/screens/components/wizard/code/WizardCodePage.tsx
index 86885472a4..acb4d9f9bb 100644
--- a/apps/website/screens/components/wizard/code/WizardCodePage.tsx
+++ b/apps/website/screens/components/wizard/code/WizardCodePage.tsx
@@ -30,7 +30,9 @@ const sections = [
number
Initially selected step, only when it is uncontrolled.
- -
+
+ 0
+
currentStep
@@ -41,9 +43,7 @@ const sections = [
Defines which step is marked as the current. The numeration starts at 0. If undefined, the component will
be uncontrolled and the step will be managed internally by the component.
-
- 0
-
+ -
mode
diff --git a/apps/website/screens/principles/localization/LocalizationPage.tsx b/apps/website/screens/principles/localization/LocalizationPage.tsx
index 29b25c2b08..20c37536e1 100644
--- a/apps/website/screens/principles/localization/LocalizationPage.tsx
+++ b/apps/website/screens/principles/localization/LocalizationPage.tsx
@@ -353,7 +353,7 @@ const sections = [
- hamburguerTitle
+ hamburgerTitle
Menu
diff --git a/apps/website/screens/principles/themes/ThemesPage.tsx b/apps/website/screens/principles/themes/ThemesPage.tsx
index 760f6ee9fb..ddeba37ef5 100644
--- a/apps/website/screens/principles/themes/ThemesPage.tsx
+++ b/apps/website/screens/principles/themes/ThemesPage.tsx
@@ -671,7 +671,7 @@ const sections = [
Font color
- hamburguerFontColor
+ hamburgerFontColor
@@ -681,12 +681,12 @@ const sections = [
- Hamburguer color
+ Hamburger color
- hamburguerIconColor
+ hamburgerIconColor
- hamburguerHoverColor (+90% of lightness)
+ hamburgerHoverColor (+90% of lightness)
diff --git a/apps/website/screens/principles/themes/examples/bloomTheme.ts b/apps/website/screens/principles/themes/examples/bloomTheme.ts
index 970f2046dd..461419993b 100644
--- a/apps/website/screens/principles/themes/examples/bloomTheme.ts
+++ b/apps/website/screens/principles/themes/examples/bloomTheme.ts
@@ -40,7 +40,7 @@ export default {
accentColor: "#000000",
fontColor: "#000000",
menuBaseColor: "#ffffff",
- hamburguerColor: "#000000",
+ hamburgerColor: "#000000",
logo: "https://assure.proxy.lambda/image/image_1674206652560.jpg",
logoResponsive: "https://assure.proxy.lambda/image/image_1674206660896.jpg",
contentColor: "#000000",
diff --git a/apps/website/screens/theme-generator/themes/schemas/advanced.schema.json b/apps/website/screens/theme-generator/themes/schemas/advanced.schema.json
index cbc0944068..58b7ee07bb 100644
--- a/apps/website/screens/theme-generator/themes/schemas/advanced.schema.json
+++ b/apps/website/screens/theme-generator/themes/schemas/advanced.schema.json
@@ -403,15 +403,8 @@
"dialog": {
"overlayColor": "color",
"backgroundColor": "color",
- "closeIconSize": "length",
- "closeIconTopPosition": "length",
- "closeIconRightPosition": "length",
"closeIconBackgroundColor": "color",
"closeIconColor": "color",
- "closeIconBorderColor": "color",
- "closeIconBorderThickness": "bWidth",
- "closeIconBorderStyle": "bStyle",
- "closeIconBorderRadius": "length",
"boxShadowOffsetX": "length",
"boxShadowOffsetY": "length",
"boxShadowBlur": "length",
@@ -548,13 +541,13 @@
"backgroundColor": "color",
"underlinedColor": "color",
"menuBackgroundColor": "color",
- "hamburguerIconColor": "color",
- "hamburguerHoverColor": "color",
+ "hamburgerIconColor": "color",
+ "hamburgerHoverColor": "color",
"overlayColor": "color",
- "hamburguerFocusColor": "color",
+ "hamburgerFocusColor": "color",
"logo": "image",
"logoResponsive": "image",
- "hamburguerTextTransform": "fTextTransform",
+ "hamburgerTextTransform": "fTextTransform",
"underlinedThickness": "bWidth",
"underlinedStyle": "bStyle",
"minHeight": "length",
@@ -569,11 +562,11 @@
"menuMobileWidth": "length",
"overlayOpacity": "alphaValue",
"overlayZindex": "integer",
- "hamburguerFontFamily": "fFamily",
- "hamburguerFontStyle": "fStyle",
- "hamburguerFontColor": "color",
- "hamburguerFontSize": "length",
- "hamburguerFontWeight": "fWeight",
+ "hamburgerFontFamily": "fFamily",
+ "hamburgerFontStyle": "fStyle",
+ "hamburgerFontColor": "color",
+ "hamburgerFontSize": "length",
+ "hamburgerFontWeight": "fWeight",
"contentColor": "color"
},
"heading": {
diff --git a/apps/website/screens/theme-generator/themes/schemas/opinionated.schema.json b/apps/website/screens/theme-generator/themes/schemas/opinionated.schema.json
index f766b055d2..f61d8dd52f 100644
--- a/apps/website/screens/theme-generator/themes/schemas/opinionated.schema.json
+++ b/apps/website/screens/theme-generator/themes/schemas/opinionated.schema.json
@@ -58,7 +58,7 @@
"accentColor": "color",
"fontColor": "color",
"menuBaseColor": "color",
- "hamburguerColor": "color",
+ "hamburgerColor": "color",
"logo": "image",
"logoResponsive": "image",
"contentColor": "color",
diff --git a/packages/lib/src/HalstackContext.tsx b/packages/lib/src/HalstackContext.tsx
index 69693bf199..500f2e7a86 100644
--- a/packages/lib/src/HalstackContext.tsx
+++ b/packages/lib/src/HalstackContext.tsx
@@ -190,10 +190,10 @@ const parseTheme = (theme: DeepPartial): AdvancedTheme => {
headerTokens.backgroundColor = theme.header?.baseColor ?? headerTokens.backgroundColor;
headerTokens.underlinedColor = theme.header?.accentColor ?? headerTokens.underlinedColor;
headerTokens.menuBackgroundColor = theme.header?.menuBaseColor ?? headerTokens.menuBackgroundColor;
- headerTokens.hamburguerFontColor = theme.header?.fontColor ?? headerTokens.hamburguerFontColor;
- headerTokens.hamburguerIconColor = theme.header?.hamburguerColor ?? headerTokens.hamburguerIconColor;
- headerTokens.hamburguerHoverColor =
- addLightness(90, theme.header?.hamburguerColor) ?? headerTokens.hamburguerHoverColor;
+ headerTokens.hamburgerFontColor = theme.header?.fontColor ?? headerTokens.hamburgerFontColor;
+ headerTokens.hamburgerIconColor = theme.header?.hamburgerColor ?? headerTokens.hamburgerIconColor;
+ headerTokens.hamburgerHoverColor =
+ addLightness(90, theme.header?.hamburgerColor) ?? headerTokens.hamburgerHoverColor;
headerTokens.logo = theme.header?.logo ?? headerTokens.logo;
headerTokens.logoResponsive = theme.header?.logoResponsive ?? headerTokens.logoResponsive;
headerTokens.contentColor = theme.header?.contentColor ?? headerTokens.contentColor;
diff --git a/packages/lib/src/accordion/Accordion.tsx b/packages/lib/src/accordion/Accordion.tsx
index 5d576f6b94..cbdea4f20b 100644
--- a/packages/lib/src/accordion/Accordion.tsx
+++ b/packages/lib/src/accordion/Accordion.tsx
@@ -6,68 +6,6 @@ import HalstackContext from "../HalstackContext";
import AccordionPropsType from "./types";
import DxcIcon from "../icon/Icon";
-const DxcAccordion = ({
- label = "",
- defaultIsExpanded,
- isExpanded,
- icon,
- assistiveText = "",
- disabled = false,
- onChange,
- children,
- margin,
- tabIndex = 0,
-}: AccordionPropsType): JSX.Element => {
- const id = useId();
- const [innerIsExpanded, setInnerIsExpanded] = useState(defaultIsExpanded ?? false);
- const colorsTheme = useContext(HalstackContext);
-
- const handleAccordionState = () => {
- if (isExpanded == null) {
- setInnerIsExpanded((innerIsCurrentlyExpanded) => !innerIsCurrentlyExpanded);
- }
- onChange?.(isExpanded != null ? !isExpanded : !innerIsExpanded);
- };
-
- return (
-
-
-
-
-
-
- {icon && (
-
- {typeof icon === "string" ? : icon}
-
- )}
- {label}
-
- {assistiveText && {assistiveText} }
-
-
-
-
-
-
- {(isExpanded ?? innerIsExpanded) && (
-
- {children}
-
- )}
-
-
- );
-};
-
const calculateWidth = (margin: AccordionPropsType["margin"]) =>
`calc(100% - ${getMargin(margin, "left")} - ${getMargin(margin, "right")})`;
@@ -191,4 +129,66 @@ const AccordionPanel = styled.div`
border-bottom-right-radius: ${(props) => props.theme.borderRadius};
`;
+const DxcAccordion = ({
+ label = "",
+ defaultIsExpanded = false,
+ isExpanded,
+ icon,
+ assistiveText = "",
+ disabled = false,
+ onChange,
+ children,
+ margin,
+ tabIndex = 0,
+}: AccordionPropsType): JSX.Element => {
+ const id = useId();
+ const [innerIsExpanded, setInnerIsExpanded] = useState(defaultIsExpanded);
+ const colorsTheme = useContext(HalstackContext);
+
+ const handleAccordionState = () => {
+ if (isExpanded == null) {
+ setInnerIsExpanded((innerIsCurrentlyExpanded) => !innerIsCurrentlyExpanded);
+ }
+ onChange?.(isExpanded != null ? !isExpanded : !innerIsExpanded);
+ };
+
+ return (
+
+
+
+
+
+
+ {icon && (
+
+ {typeof icon === "string" ? : icon}
+
+ )}
+ {label}
+
+ {assistiveText && {assistiveText} }
+
+
+
+
+
+
+ {(isExpanded ?? innerIsExpanded) && (
+
+ {children}
+
+ )}
+
+
+ );
+};
+
export default DxcAccordion;
diff --git a/packages/lib/src/alert/types.ts b/packages/lib/src/alert/types.ts
index 71c6643784..5985269862 100644
--- a/packages/lib/src/alert/types.ts
+++ b/packages/lib/src/alert/types.ts
@@ -2,31 +2,87 @@ import { ReactNode } from "react";
import { SVG } from "../common/utils";
type Action = {
+ /**
+ * The icon of the action. It can be a string with the name of the icon or an SVG component.
+ */
icon?: string | SVG;
+ /**
+ * The label of the action.
+ */
label: string;
+ /**
+ * The function that will be executed when the user clicks on the action button.
+ */
onClick: () => void;
};
type Message = {
+ /**
+ * The function that will be executed when the user clicks on the close action button.
+ */
onClose?: () => void;
+ /**
+ * The content of the message. The only Halstack component allowed within the text of an alert is the Link component,
+ * and it should be used exclusively to direct users to additional resources or relevant pages.
+ * No other components are permitted within the content of an alert.
+ */
text: ReactNode;
};
type CommonProps = {
+ /**
+ * If true, the alert will have a close button that will remove the message from the alert,
+ * only in banner and inline modes. The rest of the functionality will depend
+ * on the onClose event of each message (e.g. closing the modal alert).
+ */
closable?: boolean;
+ /**
+ * Primary action of the alert.
+ */
primaryAction?: Action;
+ /**
+ * Secondary action of the alert.
+ */
secondaryAction?: Action;
+ /**
+ * Specifies the semantic meaning of the alert.
+ */
semantic?: "error" | "info" | "success" | "warning";
+ /**
+ * Title of the alert.
+ */
title: string;
};
type ModeSpecificProps =
| {
+ /**
+ * List of messages to be displayed. Each message has a close action that will,
+ * apart from remove from the alert the current message, call the onClose if it is defined.
+ * If the message is an array, the alert will show a navigation bar to move between the messages.
+ * The modal mode only allows one message per alert. In this case, the message must be an object
+ * and the presence of the onClose attribute is mandatory, since the management of the closing
+ * of the alert relies completely on the user.
+ */
message?: Message | Message[];
+ /**
+ * The mode of the alert.
+ */
mode?: "inline" | "banner";
}
| {
+ /**
+ * List of messages to be displayed. Each message has a close action that will,
+ * apart from remove from the alert the current message, call the onClose if it is defined.
+ * If the message is an array, the alert will show a navigation bar to move between the messages.
+ * The modal mode only allows one message per alert. In this case, the message must be an object
+ * and the presence of the onClose attribute is mandatory, since the management of the closing
+ * of the alert relies completely on the user.
+ */
message: Required;
+ /**
+ * The mode of the alert.
+ */
mode: "modal";
};
diff --git a/packages/lib/src/common/variables.ts b/packages/lib/src/common/variables.ts
index 6990e4dbe3..a7349d1833 100644
--- a/packages/lib/src/common/variables.ts
+++ b/packages/lib/src/common/variables.ts
@@ -388,15 +388,8 @@ export const componentTokens = {
dialog: {
overlayColor: CoreTokens.color_grey_800_a,
backgroundColor: CoreTokens.color_white,
- closeIconSize: "24px",
- closeIconTopPosition: "20px",
- closeIconRightPosition: "20px",
closeIconBackgroundColor: CoreTokens.color_transparent,
- closeIconBorderColor: CoreTokens.border_none,
closeIconColor: CoreTokens.color_black,
- closeIconBorderThickness: CoreTokens.border_width_0,
- closeIconBorderStyle: CoreTokens.border_solid,
- closeIconBorderRadius: "2px",
boxShadowOffsetX: "0px",
boxShadowOffsetY: "1px",
boxShadowBlur: "3px",
@@ -531,15 +524,15 @@ export const componentTokens = {
},
header: {
backgroundColor: CoreTokens.color_white,
- hamburguerFocusColor: CoreTokens.color_blue_600,
- hamburguerFontFamily: CoreTokens.type_sans,
- hamburguerFontStyle: CoreTokens.type_normal,
- hamburguerFontColor: CoreTokens.color_black,
- hamburguerFontSize: "10px",
- hamburguerFontWeight: CoreTokens.type_semibold,
- hamburguerTextTransform: CoreTokens.type_uppercase,
- hamburguerIconColor: CoreTokens.color_black,
- hamburguerHoverColor: CoreTokens.color_grey_200,
+ hamburgerFocusColor: CoreTokens.color_blue_600,
+ hamburgerFontFamily: CoreTokens.type_sans,
+ hamburgerFontStyle: CoreTokens.type_normal,
+ hamburgerFontColor: CoreTokens.color_black,
+ hamburgerFontSize: "10px",
+ hamburgerFontWeight: CoreTokens.type_semibold,
+ hamburgerTextTransform: CoreTokens.type_uppercase,
+ hamburgerIconColor: CoreTokens.color_black,
+ hamburgerHoverColor: CoreTokens.color_grey_200,
logo: "",
logoResponsive: "",
logoHeight: "40px",
@@ -1353,7 +1346,7 @@ export type OpinionatedTheme = {
accentColor: string;
fontColor: string;
menuBaseColor: string;
- hamburguerColor: string;
+ hamburgerColor: string;
logo: string;
logoResponsive: string;
contentColor: string;
@@ -1519,7 +1512,7 @@ export const defaultTranslatedComponentLabels = {
},
header: {
closeIcon: "Close menu",
- hamburguerTitle: "Menu",
+ hamburgerTitle: "Menu",
},
numberInput: {
valueGreaterThanOrEqualToErrorMessage: (value: number) => `Value must be greater than or equal to ${value}.`,
diff --git a/packages/lib/src/dialog/Dialog.accessibility.test.tsx b/packages/lib/src/dialog/Dialog.accessibility.test.tsx
index 1f4001bbc8..e61471afdd 100644
--- a/packages/lib/src/dialog/Dialog.accessibility.test.tsx
+++ b/packages/lib/src/dialog/Dialog.accessibility.test.tsx
@@ -2,6 +2,16 @@ import { render } from "@testing-library/react";
import { axe } from "../../test/accessibility/axe-helper";
import DxcDialog from "./Dialog";
+(global as any).globalThis = global;
+(global as any).DOMRect = {
+ fromRect: () => ({ top: 0, left: 0, bottom: 0, right: 0, width: 0, height: 0 }),
+};
+(global as any).ResizeObserver = class ResizeObserver {
+ observe() {}
+ unobserve() {}
+ disconnect() {}
+};
+
describe("Dialog component accessibility tests", () => {
it("Should not have basic accessibility issues", async () => {
// baseElement is needed when using React Portals
@@ -10,13 +20,11 @@ describe("Dialog component accessibility tests", () => {
expect(results).toHaveNoViolations();
});
it("Should not have basic accessibility issues for close button not visible", async () => {
- // baseElement is needed when using React Portals
const { baseElement } = render(Dialog text );
const results = await axe(baseElement);
expect(results).toHaveNoViolations();
});
it("Should not have basic accessibility issues for overlay not visible", async () => {
- // baseElement is needed when using React Portals
const { baseElement } = render(Dialog text );
const results = await axe(baseElement);
expect(results).toHaveNoViolations();
diff --git a/packages/lib/src/dialog/Dialog.tsx b/packages/lib/src/dialog/Dialog.tsx
index d86d7f6412..b5e4af27e2 100644
--- a/packages/lib/src/dialog/Dialog.tsx
+++ b/packages/lib/src/dialog/Dialog.tsx
@@ -2,7 +2,7 @@ import { useContext, useEffect } from "react";
import { createPortal } from "react-dom";
import styled, { createGlobalStyle, ThemeProvider } from "styled-components";
import { responsiveSizes } from "../common/variables";
-import DxcIcon from "../icon/Icon";
+import DxcActionIcon from "../action-icon/ActionIcon";
import HalstackContext, { HalstackLanguageContext } from "../HalstackContext";
import FocusLock from "../utils/FocusLock";
import DialogPropsType from "./types";
@@ -48,45 +48,18 @@ const Dialog = styled.div<{ closable: DialogPropsType["closable"] }>`
}
`;
-const CloseIconAction = styled.button`
- all: unset;
+const CloseIconActionContainer = styled.div`
position: absolute;
top: 24px;
right: 24px;
- display: flex;
- align-items: center;
- justify-content: center;
- background-color: ${(props) => props.theme.closeIconBackgroundColor};
- box-shadow: 0 0 0 2px transparent;
- color: ${(props) => props.theme.closeIconColor};
- border-radius: ${(props) => props.theme.closeIconBorderRadius};
- border-width: ${(props) => props.theme.closeIconBorderThickness};
- border-style: ${(props) => props.theme.closeIconBorderStyle};
- border-color: ${(props) => props.theme.closeIconBorderColor};
- cursor: pointer;
- z-index: 1;
-
- &:focus {
- outline: none;
- box-shadow: 0 0 0 2px #0095ff;
- }
- &:hover {
- background-color: #f2f2f2;
- }
- &:active {
- background-color: #cccccc;
- }
- span {
- font-size: ${(props) => props.theme.closeIconSize};
- }
`;
const DxcDialog = ({
+ children,
closable = true,
+ onBackgroundClick,
onCloseClick,
- children,
overlay = true,
- onBackgroundClick,
tabIndex = 0,
}: DialogPropsType): JSX.Element => {
const colorsTheme = useContext(HalstackContext);
@@ -111,26 +84,26 @@ const DxcDialog = ({
{createPortal(
- {overlay && (
- {
- onBackgroundClick?.();
- }}
- />
- )}
-
+ {overlay && }
+
{children}
{closable && (
- {
- onCloseClick?.();
+
-
-
+
+
+
+
)}
diff --git a/packages/lib/src/file-input/FileInput.stories.tsx b/packages/lib/src/file-input/FileInput.stories.tsx
index 9adc64af92..be4fbdbb9f 100644
--- a/packages/lib/src/file-input/FileInput.stories.tsx
+++ b/packages/lib/src/file-input/FileInput.stories.tsx
@@ -3,7 +3,6 @@ import ExampleContainer from "../../.storybook/components/ExampleContainer";
import Title from "../../.storybook/components/Title";
import { HalstackProvider } from "../HalstackContext";
import DxcFileInput from "./FileInput";
-import FileItem from "./FileItem";
export default {
title: "File Input",
@@ -79,46 +78,6 @@ const opinionatedTheme = {
const FileInput = () => (
<>
-
-
-
- {}}
- tabIndex={0}
- />
-
-
-
- {}}
- tabIndex={0}
- />
-
-
-
- {}}
- tabIndex={0}
- />
-
diff --git a/packages/lib/src/file-input/FileInput.tsx b/packages/lib/src/file-input/FileInput.tsx
index 0a8d1a64ec..1b1301536a 100644
--- a/packages/lib/src/file-input/FileInput.tsx
+++ b/packages/lib/src/file-input/FileInput.tsx
@@ -5,32 +5,127 @@ import { spaces } from "../common/variables";
import FileItem from "./FileItem";
import FileInputPropsType, { FileData, RefType } from "./types";
import HalstackContext, { HalstackLanguageContext } from "../HalstackContext";
+import { getFilePreview, isFileIncluded } from "./utils";
-const getFilePreview = async (file: File): Promise => {
- if (file.type.includes("video")) return "filled_movie";
- else if (file.type.includes("audio")) return "music_video";
- else if (file.type.includes("image")) {
- return new Promise((resolve) => {
- const reader = new FileReader();
- reader.readAsDataURL(file);
- reader.onload = (e) => {
- resolve(e.target?.result as string);
- };
- });
- } else return "draft";
-};
+const FileInputContainer = styled.div<{ margin: FileInputPropsType["margin"] }>`
+ display: flex;
+ flex-direction: column;
+ margin: ${(props) => (props.margin && typeof props.margin !== "object" ? spaces[props.margin] : "0px")};
+ margin-top: ${(props) =>
+ props.margin && typeof props.margin === "object" && props.margin.top ? spaces[props.margin.top] : ""};
+ margin-right: ${(props) =>
+ props.margin && typeof props.margin === "object" && props.margin.right ? spaces[props.margin.right] : ""};
+ margin-bottom: ${(props) =>
+ props.margin && typeof props.margin === "object" && props.margin.bottom ? spaces[props.margin.bottom] : ""};
+ margin-left: ${(props) =>
+ props.margin && typeof props.margin === "object" && props.margin.left ? spaces[props.margin.left] : ""};
+ width: fit-content;
+`;
+
+const Label = styled.label<{ disabled: FileInputPropsType["disabled"] }>`
+ color: ${(props) => (props.disabled ? props.theme.disabledLabelFontColor : props.theme.labelFontColor)};
+ font-family: ${(props) => props.theme.labelFontFamily};
+ font-size: ${(props) => props.theme.labelFontSize};
+ font-weight: ${(props) => props.theme.labelFontWeight};
+ line-height: ${(props) => props.theme.labelLineHeight};
+`;
+
+const HelperText = styled.span<{ disabled: FileInputPropsType["disabled"] }>`
+ color: ${(props) => (props.disabled ? props.theme.disabledHelperTextFontColor : props.theme.helperTextFontColor)};
+ font-family: ${(props) => props.theme.helperTextFontFamily};
+ font-size: ${(props) => props.theme.helperTextFontSize};
+ font-weight: ${(props) => props.theme.helperTextFontWeight};
+ line-height: ${(props) => props.theme.helperTextLineHeight};
+`;
+
+const FileContainer = styled.div<{ singleFileMode: boolean }>`
+ display: flex;
+ ${(props) =>
+ props.singleFileMode ? "flex-direction: row; column-gap: 0.25rem;" : "flex-direction: column; row-gap: 0.25rem;"}
+ margin-top: 0.25rem;
+`;
+
+const ValueInput = styled.input`
+ display: none;
+`;
+
+const FileItemListContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ row-gap: 0.25rem;
+`;
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ row-gap: 0.25rem;
+ margin-top: 0.25rem;
+`;
+
+const DragDropArea = styled.div<{
+ mode: FileInputPropsType["mode"];
+ disabled: FileInputPropsType["disabled"];
+ isDragging: boolean;
+}>`
+ box-sizing: border-box;
+ display: flex;
+ ${(props) =>
+ props.mode === "filedrop"
+ ? "flex-direction: row; column-gap: 0.75rem; height: 48px;"
+ : "justify-content: center; flex-direction: column; row-gap: 0.5rem; height: 160px;"}
+ align-items: center;
+ width: 320px;
+ padding: ${(props) =>
+ props.mode === "filedrop"
+ ? `calc(4px - ${props.theme.dropBorderThickness}) 1rem calc(4px - ${props.theme.dropBorderThickness}) calc(4px - ${props.theme.dropBorderThickness})`
+ : "1rem"};
+ overflow: hidden;
+ box-shadow: 0 0 0 2px transparent;
+ border-radius: ${(props) => props.theme.dropBorderRadius};
+ border-width: ${(props) => props.theme.dropBorderThickness};
+ border-style: ${(props) => props.theme.dropBorderStyle};
+ border-color: ${(props) => (props.disabled ? props.theme.disabledDropBorderColor : props.theme.dropBorderColor)};
+ ${(props) =>
+ props.isDragging &&
+ `
+ background-color: ${props.theme.dragoverDropBackgroundColor};
+ border-color: transparent;
+ box-shadow: 0 0 0 2px ${props.theme.focusDropBorderColor};
+ `}
+ cursor: ${(props) => props.disabled && "not-allowed"};
+`;
+
+const DropzoneLabel = styled.div<{ disabled: FileInputPropsType["disabled"] }>`
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ -webkit-line-clamp: 3;
+ text-align: center;
+ color: ${(props) => (props.disabled ? props.theme.disabledDropLabelFontColor : props.theme.dropLabelFontColor)};
+ font-family: ${(props) => props.theme.dropLabelFontFamily};
+ font-size: ${(props) => props.theme.dropLabelFontSize};
+ font-weight: ${(props) => props.theme.dropLabelFontWeight};
+`;
+
+const FiledropLabel = styled.span<{ disabled: FileInputPropsType["disabled"] }>`
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ color: ${(props) => (props.disabled ? props.theme.disabledDropLabelFontColor : props.theme.dropLabelFontColor)};
+ font-family: ${(props) => props.theme.dropLabelFontFamily};
+ font-size: ${(props) => props.theme.dropLabelFontSize};
+ font-weight: ${(props) => props.theme.dropLabelFontWeight};
+`;
-const isFileIncluded = (file: FileData, fileList: FileData[]) => {
- const fileListInfo = fileList.map((existingFile) => existingFile.file);
- return fileListInfo.some(
- ({ name, size, type, lastModified, webkitRelativePath }) =>
- name === file.file.name &&
- size === file.file.size &&
- type === file.file.type &&
- lastModified === file.file.lastModified &&
- webkitRelativePath === file.file.webkitRelativePath
- );
-};
+const ErrorMessage = styled.div`
+ color: ${(props) => props.theme.errorMessageFontColor};
+ font-family: ${(props) => props.theme.errorMessageFontFamily};
+ font-size: ${(props) => props.theme.errorMessageFontSize};
+ font-weight: ${(props) => props.theme.errorMessageFontWeight};
+ line-height: ${(props) => props.theme.errorMessageLineHeight};
+ margin-top: 0.25rem;
+`;
const DxcFileInput = forwardRef(
(
@@ -188,7 +283,7 @@ const DxcFileInput = forwardRef(
tabIndex={tabIndex}
/>
{files.length > 0 && (
-
+
{files.map((file, index) => (
(
)}
{files.length > 0 && (
-
+
{files.map((file, index) => (
(
}
);
-const FileInputContainer = styled.div<{ margin: FileInputPropsType["margin"] }>`
- display: flex;
- flex-direction: column;
- margin: ${(props) => (props.margin && typeof props.margin !== "object" ? spaces[props.margin] : "0px")};
- margin-top: ${(props) =>
- props.margin && typeof props.margin === "object" && props.margin.top ? spaces[props.margin.top] : ""};
- margin-right: ${(props) =>
- props.margin && typeof props.margin === "object" && props.margin.right ? spaces[props.margin.right] : ""};
- margin-bottom: ${(props) =>
- props.margin && typeof props.margin === "object" && props.margin.bottom ? spaces[props.margin.bottom] : ""};
- margin-left: ${(props) =>
- props.margin && typeof props.margin === "object" && props.margin.left ? spaces[props.margin.left] : ""};
- width: fit-content;
-`;
-
-const Label = styled.label<{ disabled: FileInputPropsType["disabled"] }>`
- color: ${(props) => (props.disabled ? props.theme.disabledLabelFontColor : props.theme.labelFontColor)};
- font-family: ${(props) => props.theme.labelFontFamily};
- font-size: ${(props) => props.theme.labelFontSize};
- font-weight: ${(props) => props.theme.labelFontWeight};
- line-height: ${(props) => props.theme.labelLineHeight};
-`;
-
-const HelperText = styled.span<{ disabled: FileInputPropsType["disabled"] }>`
- color: ${(props) => (props.disabled ? props.theme.disabledHelperTextFontColor : props.theme.helperTextFontColor)};
- font-family: ${(props) => props.theme.helperTextFontFamily};
- font-size: ${(props) => props.theme.helperTextFontSize};
- font-weight: ${(props) => props.theme.helperTextFontWeight};
- line-height: ${(props) => props.theme.helperTextLineHeight};
-`;
-
-const FileContainer = styled.div<{ singleFileMode: boolean }>`
- display: flex;
- ${(props) =>
- props.singleFileMode ? "flex-direction: row; column-gap: 0.25rem;" : "flex-direction: column; row-gap: 0.25rem;"}
- margin-top: 0.25rem;
-`;
-
-const ValueInput = styled.input`
- display: none;
-`;
-
-const FileItemListContainer = styled.div`
- display: flex;
- flex-direction: column;
- row-gap: 0.25rem;
-`;
-
-const Container = styled.div`
- display: flex;
- flex-direction: column;
- row-gap: 0.25rem;
- margin-top: 0.25rem;
-`;
-
-const DragDropArea = styled.div<{
- mode: FileInputPropsType["mode"];
- disabled: FileInputPropsType["disabled"];
- isDragging: boolean;
-}>`
- box-sizing: border-box;
- display: flex;
- ${(props) =>
- props.mode === "filedrop"
- ? "flex-direction: row; column-gap: 0.75rem; height: 48px;"
- : "justify-content: center; flex-direction: column; row-gap: 0.5rem; height: 160px;"}
- align-items: center;
- width: 320px;
- padding: ${(props) =>
- props.mode === "filedrop"
- ? `calc(4px - ${props.theme.dropBorderThickness}) 1rem calc(4px - ${props.theme.dropBorderThickness}) calc(4px - ${props.theme.dropBorderThickness})`
- : "1rem"};
- overflow: hidden;
- box-shadow: 0 0 0 2px transparent;
- border-radius: ${(props) => props.theme.dropBorderRadius};
- border-width: ${(props) => props.theme.dropBorderThickness};
- border-style: ${(props) => props.theme.dropBorderStyle};
- border-color: ${(props) => (props.disabled ? props.theme.disabledDropBorderColor : props.theme.dropBorderColor)};
- ${(props) =>
- props.isDragging &&
- `
- background-color: ${props.theme.dragoverDropBackgroundColor};
- border-color: transparent;
- box-shadow: 0 0 0 2px ${props.theme.focusDropBorderColor};
- `}
- cursor: ${(props) => props.disabled && "not-allowed"};
-`;
-
-const DropzoneLabel = styled.div<{ disabled: FileInputPropsType["disabled"] }>`
- display: -webkit-box;
- -webkit-box-orient: vertical;
- overflow: hidden;
- text-overflow: ellipsis;
- -webkit-line-clamp: 3;
- text-align: center;
- color: ${(props) => (props.disabled ? props.theme.disabledDropLabelFontColor : props.theme.dropLabelFontColor)};
- font-family: ${(props) => props.theme.dropLabelFontFamily};
- font-size: ${(props) => props.theme.dropLabelFontSize};
- font-weight: ${(props) => props.theme.dropLabelFontWeight};
-`;
-
-const FiledropLabel = styled.span<{ disabled: FileInputPropsType["disabled"] }>`
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- color: ${(props) => (props.disabled ? props.theme.disabledDropLabelFontColor : props.theme.dropLabelFontColor)};
- font-family: ${(props) => props.theme.dropLabelFontFamily};
- font-size: ${(props) => props.theme.dropLabelFontSize};
- font-weight: ${(props) => props.theme.dropLabelFontWeight};
-`;
-
-const ErrorMessage = styled.div`
- color: ${(props) => props.theme.errorMessageFontColor};
- font-family: ${(props) => props.theme.errorMessageFontFamily};
- font-size: ${(props) => props.theme.errorMessageFontSize};
- font-weight: ${(props) => props.theme.errorMessageFontWeight};
- line-height: ${(props) => props.theme.errorMessageLineHeight};
- margin-top: 0.25rem;
-`;
-
export default DxcFileInput;
diff --git a/packages/lib/src/file-input/FileItem.tsx b/packages/lib/src/file-input/FileItem.tsx
index 1175f483a9..02737b5006 100644
--- a/packages/lib/src/file-input/FileItem.tsx
+++ b/packages/lib/src/file-input/FileItem.tsx
@@ -1,62 +1,10 @@
-import { memo, useContext } from "react";
-import styled, { ThemeProvider } from "styled-components";
+import { memo, useContext, useId } from "react";
+import styled from "styled-components";
import DxcFlex from "../flex/Flex";
import { FileItemProps } from "./types";
import DxcIcon from "../icon/Icon";
import DxcActionIcon from "../action-icon/ActionIcon";
-import HalstackContext, { HalstackLanguageContext } from "../HalstackContext";
-
-const FileItem = ({
- fileName = "",
- error = "",
- singleFileMode,
- showPreview,
- preview,
- type,
- onDelete,
- tabIndex,
-}: FileItemProps): JSX.Element => {
- const colorsTheme = useContext(HalstackContext);
- const translatedLabels = useContext(HalstackLanguageContext);
-
- const getIconAriaLabel = () => (type.includes("video") ? "video" : type.includes("audio") ? "audio" : "file");
-
- return (
-
-
- {showPreview &&
- (type.includes("image") ? (
-
- ) : (
-
-
-
- ))}
-
- {fileName}
-
- {error && (
-
-
-
- )}
- onDelete(fileName)}
- icon="close"
- tabIndex={tabIndex}
- title={translatedLabels.fileInput.deleteFileActionTitle}
- />
-
- {error && !singleFileMode && (
-
- {error}
-
- )}
-
-
-
- );
-};
+import { HalstackLanguageContext } from "../HalstackContext";
const MainContainer = styled.div<{
error: FileItemProps["error"];
@@ -144,4 +92,52 @@ const ErrorMessage = styled.span`
line-height: ${(props) => props.theme.errorMessageLineHeight};
`;
+const FileItem = ({
+ fileName = "",
+ error = "",
+ singleFileMode,
+ showPreview,
+ preview,
+ type,
+ onDelete,
+ tabIndex,
+}: FileItemProps): JSX.Element => {
+ const translatedLabels = useContext(HalstackLanguageContext);
+ const fileNameId = useId();
+
+ return (
+
+ {showPreview &&
+ (type.includes("image") ? (
+
+ ) : (
+
+
+
+ ))}
+
+ {fileName}
+
+ {error && (
+
+
+
+ )}
+ onDelete(fileName)}
+ icon="close"
+ tabIndex={tabIndex}
+ title={translatedLabels.fileInput.deleteFileActionTitle}
+ />
+
+ {error && !singleFileMode && (
+
+ {error}
+
+ )}
+
+
+ );
+};
+
export default memo(FileItem);
diff --git a/packages/lib/src/file-input/utils.ts b/packages/lib/src/file-input/utils.ts
new file mode 100644
index 0000000000..7b76761e73
--- /dev/null
+++ b/packages/lib/src/file-input/utils.ts
@@ -0,0 +1,27 @@
+import { FileData } from "./types";
+
+export const getFilePreview = async (file: File): Promise => {
+ if (file.type.includes("video")) return "filled_movie";
+ else if (file.type.includes("audio")) return "music_video";
+ else if (file.type.includes("image")) {
+ return new Promise((resolve) => {
+ const reader = new FileReader();
+ reader.readAsDataURL(file);
+ reader.onload = (e) => {
+ resolve(e.target?.result as string);
+ };
+ });
+ } else return "draft";
+};
+
+export const isFileIncluded = (file: FileData, fileList: FileData[]) => {
+ const fileListInfo = fileList.map((existingFile) => existingFile.file);
+ return fileListInfo.some(
+ ({ name, size, type, lastModified, webkitRelativePath }) =>
+ name === file.file.name &&
+ size === file.file.size &&
+ type === file.file.type &&
+ lastModified === file.file.lastModified &&
+ webkitRelativePath === file.file.webkitRelativePath
+ );
+};
\ No newline at end of file
diff --git a/packages/lib/src/header/Header.stories.tsx b/packages/lib/src/header/Header.stories.tsx
index e7b0ccf14a..4c76a3b6d7 100644
--- a/packages/lib/src/header/Header.stories.tsx
+++ b/packages/lib/src/header/Header.stories.tsx
@@ -57,7 +57,7 @@ const opinionatedTheme = {
accentColor: "#000000",
fontColor: "#000000",
menuBaseColor: "#ffffff",
- hamburguerColor: "#000000",
+ hamburgerColor: "#000000",
logo: "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b8/2021_Facebook_icon.svg/2048px-2021_Facebook_icon.svg.png",
logoResponsive:
"https://upload.wikimedia.org/wikipedia/commons/thumb/b/b8/2021_Facebook_icon.svg/2048px-2021_Facebook_icon.svg.png",
diff --git a/packages/lib/src/header/Header.tsx b/packages/lib/src/header/Header.tsx
index ba23eec1de..2dd9784283 100644
--- a/packages/lib/src/header/Header.tsx
+++ b/packages/lib/src/header/Header.tsx
@@ -115,7 +115,7 @@ const DxcHeader = ({
- {translatedLabels.header.hamburguerTitle}
+ {translatedLabels.header.hamburgerTitle}
@@ -224,23 +224,23 @@ const HamburgerTrigger = styled.button`
border-radius: 2px;
background-color: transparent;
:hover {
- background-color: ${(props) => props.theme.hamburguerHoverColor};
+ background-color: ${(props) => props.theme.hamburgerHoverColor};
}
&:focus {
- outline: ${(props) => props.theme.hamburguerFocusColor} auto 1px;
+ outline: ${(props) => props.theme.hamburgerFocusColor} auto 1px;
}
& > svg {
- fill: ${(props) => props.theme.hamburguerIconColor};
+ fill: ${(props) => props.theme.hamburgerIconColor};
}
& > span {
font-size: 24px;
}
- font-family: ${(props) => props.theme.hamburguerFontFamily};
- font-style: ${(props) => props.theme.hamburguerFontStyle};
- font-size: ${(props) => props.theme.hamburguerFontSize};
- text-transform: ${(props) => props.theme.hamburguerTextTransform};
- font-weight: ${(props) => props.theme.hamburguerFontWeight};
- color: ${(props) => props.theme.hamburguerFontColor};
+ font-family: ${(props) => props.theme.hamburgerFontFamily};
+ font-style: ${(props) => props.theme.hamburgerFontStyle};
+ font-size: ${(props) => props.theme.hamburgerFontSize};
+ text-transform: ${(props) => props.theme.hamburgerTextTransform};
+ font-weight: ${(props) => props.theme.hamburgerFontWeight};
+ color: ${(props) => props.theme.hamburgerFontColor};
`;
const ResponsiveMenu = styled.div<{ hasVisibility: boolean }>`
@@ -288,7 +288,7 @@ const CloseAction = styled.button`
:focus,
:focus-visible {
- outline: ${(props) => props.theme.hamburguerFocusColor} auto 1px;
+ outline: ${(props) => props.theme.hamburgerFocusColor} auto 1px;
}
font-size: 24px;
svg {
diff --git a/packages/lib/src/icon/Icon.tsx b/packages/lib/src/icon/Icon.tsx
index d49b99db7a..cc2b977e6f 100644
--- a/packages/lib/src/icon/Icon.tsx
+++ b/packages/lib/src/icon/Icon.tsx
@@ -3,7 +3,6 @@ import styled from "styled-components";
const DxcIcon = ({ icon }: { icon: string }): JSX.Element => (
(
<>
-
+
+
+
-
-
-
-
+
+
+
+
diff --git a/packages/lib/src/progress-bar/ProgressBar.test.tsx b/packages/lib/src/progress-bar/ProgressBar.test.tsx
index c62c6302bb..e470277bcc 100644
--- a/packages/lib/src/progress-bar/ProgressBar.test.tsx
+++ b/packages/lib/src/progress-bar/ProgressBar.test.tsx
@@ -2,59 +2,38 @@ import { render } from "@testing-library/react";
import DxcProgressBar from "./ProgressBar";
describe("ProgressBar component tests", () => {
- test("ProgressBar renders with label and helperText", () => {
- const { getByText } = render( );
- expect(getByText("test-label")).toBeTruthy();
- expect(getByText("helper-text")).toBeTruthy();
- });
-
- test("ProgressBar renders with default value", () => {
- const value = 0;
- const { getByText, getByRole } = render( );
+ test("ProgressBar renders no value when it is indeterminate", () => {
+ const { queryByText, getByRole } = render( );
const progressBar = getByRole("progressbar");
- expect(getByText("0 %")).toBeTruthy();
- expect(progressBar.getAttribute("aria-valuenow")).toEqual(value.toString());
+ expect(queryByText("0 %")).toBeFalsy();
+ expect(progressBar.getAttribute("aria-valuenow")).toBeNull();
});
-
- test("ProgressBar renders with value", () => {
- const value = 25;
- const { getByText, getByRole } = render( );
+ test("ProgressBar renders with value when it is determinate and has the flag showValue set to true", () => {
+ const { getByText, getByRole } = render( );
const progressBar = getByRole("progressbar");
expect(getByText("25 %")).toBeTruthy();
- expect(progressBar.getAttribute("aria-valuenow")).toEqual(value.toString());
+ expect(progressBar.getAttribute("aria-valuenow")).toEqual("25");
+ });
+ test("ProgressBar doesn't render with value when it is determinate but has the flag showValue set to false", () => {
+ const { queryByText, getByRole } = render( );
+ const progressBar = getByRole("progressbar");
+ expect(queryByText("25 %")).toBeFalsy();
+ expect(progressBar.getAttribute("aria-valuenow")).toEqual("25");
});
-
test("ProgressBar renders with negative value", () => {
- const value = 0;
- const { getByText, getByRole } = render( );
+ const { getByText, getByRole } = render( );
const progressBar = getByRole("progressbar");
expect(getByText("0 %")).toBeTruthy();
- expect(progressBar.getAttribute("aria-valuenow")).toEqual(value.toString());
+ expect(progressBar.getAttribute("aria-valuenow")).toEqual("0");
});
-
test("ProgressBar renders as indeterminate", () => {
- const { getByRole } = render( );
- const progressBar = getByRole("progressbar");
- expect(progressBar.getAttribute("aria-valuenow")).toBe(null);
- });
-
- test("Overlay progressBar renders with label and helperText", () => {
- const { getByText } = render( );
- expect(getByText("test-label")).toBeTruthy();
- expect(getByText("helper-text")).toBeTruthy();
- });
-
- test("Overlay progressBar renders with default value", () => {
- const value = 0;
- const { getByText, getByRole } = render( );
+ const { getByRole } = render( );
const progressBar = getByRole("progressbar");
- expect(getByText("0 %")).toBeTruthy();
- expect(progressBar.getAttribute("aria-valuenow")).toEqual(value.toString());
+ expect(progressBar.getAttribute("aria-valuenow")).toBeNull();
});
-
test("Overlay progressBar renders with value", () => {
const value = 25;
- const { getByText, getByRole } = render( );
+ const { getByText, getByRole } = render( );
const progressBar = getByRole("progressbar");
expect(getByText("25 %")).toBeTruthy();
expect(progressBar.getAttribute("aria-valuenow")).toEqual(value.toString());
diff --git a/packages/lib/src/progress-bar/ProgressBar.tsx b/packages/lib/src/progress-bar/ProgressBar.tsx
index 5b0bb23f25..2c6170ac19 100644
--- a/packages/lib/src/progress-bar/ProgressBar.tsx
+++ b/packages/lib/src/progress-bar/ProgressBar.tsx
@@ -1,58 +1,11 @@
-import { useContext, useEffect, useState } from "react";
+import { useContext, useEffect, useId, useState } from "react";
import styled, { ThemeProvider } from "styled-components";
import { spaces } from "../common/variables";
import HalstackContext from "../HalstackContext";
import ProgressBarPropsType from "./types";
+import DxcFlex from "../flex/Flex";
-const DxcProgressBar = ({
- label = "",
- helperText = "",
- overlay = false,
- value,
- showValue = false,
- margin,
-}: ProgressBarPropsType): JSX.Element => {
- const colorsTheme = useContext(HalstackContext);
- const [valueProgressBar, setValueProgressBar] = useState(0);
-
- useEffect(() => {
- setValueProgressBar(
- value === null || value === undefined || value < 0 ? 0 : value >= 0 && value <= 100 ? value : 100
- );
- }, [value]);
-
- return (
-
-
-
-
- {label}
-
- {valueProgressBar} %
-
-
-
-
-
- {helperText && {helperText} }
-
-
-
- );
-};
-
-const BackgroundProgressBar = styled.div<{
+const Overlay = styled.div<{
overlay: ProgressBarPropsType["overlay"];
}>`
${({ overlay, theme }) =>
@@ -69,19 +22,18 @@ const BackgroundProgressBar = styled.div<{
left: 0;
right: 0;
z-index: 1300;`
- : `background-color: "transparent";`}
+ : `background-color: transparent;`}
display: flex;
flex-wrap: wrap;
min-width: 100px;
width: 100%;
`;
-const ProgressBarContainer = styled.div<{
+const MainContainer = styled.div<{
overlay: ProgressBarPropsType["overlay"];
margin: ProgressBarPropsType["margin"];
}>`
- z-index: ${(props) => (props.overlay === true && "100") || "0"};
- width: ${(props) => (props.overlay === true ? "80%" : "100%")};
+ width: ${(props) => (props.overlay ? "80%" : "100%")};
margin: ${(props) => (props.margin && typeof props.margin !== "object" ? spaces[props.margin] : "0px")};
margin-top: ${(props) =>
props.margin && typeof props.margin === "object" && props.margin.top ? spaces[props.margin.top] : ""};
@@ -91,17 +43,10 @@ const ProgressBarContainer = styled.div<{
props.margin && typeof props.margin === "object" && props.margin.bottom ? spaces[props.margin.bottom] : ""};
margin-left: ${(props) =>
props.margin && typeof props.margin === "object" && props.margin.left ? spaces[props.margin.left] : ""};
-`;
-
-const InfoProgressBar = styled.div`
display: flex;
- flex-direction: row;
- width: 685px;
- flex-wrap: wrap;
- width: 100%;
- margin-bottom: 8px;
- align-items: baseline;
- justify-content: space-between;
+ flex-direction: column;
+ gap: 0.5rem;
+ z-index: ${(props) => props.overlay ? "100" : "0"};
`;
const ProgressBarLabel = styled.div<{
@@ -112,7 +57,7 @@ const ProgressBarLabel = styled.div<{
font-size: ${(props) => props.theme.labelFontSize};
font-weight: ${(props) => props.theme.labelFontWeight};
text-transform: ${(props) => props.theme.labelFontTextTransform};
- color: ${(props) => (props.overlay === true ? props.theme.overlayFontColor : props.theme.labelFontColor)};
+ color: ${(props) => (props.overlay ? props.theme.overlayFontColor : props.theme.labelFontColor)};
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -121,22 +66,18 @@ const ProgressBarLabel = styled.div<{
const ProgressBarProgress = styled.div<{
overlay: ProgressBarPropsType["overlay"];
- showValue: ProgressBarPropsType["showValue"];
- value: ProgressBarPropsType["value"];
}>`
+ flex-shrink: 0;
+ color: ${(props) => (props.overlay ? props.theme.overlayFontColor : props.theme.valueFontColor)};
font-family: ${(props) => props.theme.valueFontFamily};
font-style: ${(props) => props.theme.valueFontStyle};
font-size: ${(props) => props.theme.valueFontSize};
font-weight: ${(props) => props.theme.valueFontWeight};
text-transform: ${(props) => props.theme.valueFontTextTransform};
- color: ${(props) => (props.overlay === true ? props.theme.overlayFontColor : props.theme.valueFontColor)};
- display: ${(props) =>
- (props.value !== undefined && props.value !== null && props.showValue === true && "block") || "none"};
- flex-shrink: 0;
`;
const HelperText = styled.span<{ overlay: ProgressBarPropsType["overlay"] }>`
- color: ${(props) => (props.overlay === true ? props.theme.overlayFontColor : props.theme.helperTextFontColor)};
+ color: ${(props) => (props.overlay ? props.theme.overlayFontColor : props.theme.helperTextFontColor)};
font-family: ${(props) => props.theme.helperTextFontFamily};
font-size: ${(props) => props.theme.helperTextFontSize};
font-style: ${(props) => props.theme.helperTextFontStyle};
@@ -147,34 +88,29 @@ const HelperText = styled.span<{ overlay: ProgressBarPropsType["overlay"] }>`
const LinearProgress = styled.div<{
helperText: ProgressBarPropsType["helperText"];
}>`
+ position: relative;
+ border-radius: ${(props) => props.theme.borderRadius};
height: ${(props) => props.theme.thickness};
background-color: ${(props) => props.theme.totalLineColor};
- border-radius: ${(props) => props.theme.borderRadius};
- margin-bottom: ${(props) => props.helperText !== "" && "8px"};
overflow: hidden;
- position: relative;
`;
const LinearProgressBar = styled.span<{
variant: "determinate" | "indeterminate";
value: ProgressBarPropsType["value"];
- container: string;
}>`
- background-color: ${(props) => props.theme.trackLineColor};
- transform: ${(props) => `translateX(-${props.variant === "determinate" ? 100 - (props.value ?? 0) : 0}%)`};
+ position: absolute;
top: 0;
- left: 0;
- width: 100%;
bottom: 0;
- position: absolute;
+ left: 0;
+ width: ${(props) => props.variant === "indeterminate" ? "auto" : "100%"};
+ transform: ${(props) => `translateX(-${props.variant === "determinate" ? 100 - (props.value ?? 0) : 0}%)`};
transition: ${(props) => (props.variant === "determinate" ? "transform .4s linear" : "transform 0.2s linear")};
transform-origin: left;
- ${(props) => props.variant === "indeterminate" && "width: auto;"};
+ background-color: ${(props) => props.theme.trackLineColor};
${(props) =>
props.variant === "indeterminate"
- ? props.container === "first"
- ? "animation: keyframes-indeterminate-first 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;"
- : "animation: keyframes-indeterminate-second 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) 1.15s infinite;"
+ ? "animation: keyframes-indeterminate-first 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;"
: ""};
@keyframes keyframes-indeterminate-first {
@@ -208,4 +144,52 @@ const LinearProgressBar = styled.span<{
}
`;
+const DxcProgressBar = ({
+ label,
+ helperText,
+ overlay,
+ value,
+ showValue,
+ margin,
+}: ProgressBarPropsType): JSX.Element => {
+ const colorsTheme = useContext(HalstackContext);
+ const labelId = `label-${useId()}`;
+ const [innerValue, setInnerValue] = useState();
+
+ useEffect(() => {
+ if (value != null) setInnerValue(value < 0 ? 0 : value > 100 ? 100 : value);
+ }, [value]);
+
+ return (
+
+
+
+
+ {label && (
+
+ {label}
+
+ )}
+ {innerValue != null && showValue && (
+ {innerValue} %
+ )}
+
+
+
+
+ {helperText && {helperText} }
+
+
+
+ );
+};
+
export default DxcProgressBar;
diff --git a/packages/lib/src/slider/Slider.tsx b/packages/lib/src/slider/Slider.tsx
index 58abe0bf63..f0e868b79d 100644
--- a/packages/lib/src/slider/Slider.tsx
+++ b/packages/lib/src/slider/Slider.tsx
@@ -6,150 +6,6 @@ import { getMargin } from "../common/utils";
import HalstackContext from "../HalstackContext";
import SliderPropsType, { RefType } from "./types";
-const DxcSlider = forwardRef(
- (
- {
- label = "",
- name = "",
- defaultValue,
- value,
- helperText = "",
- minValue = 0,
- maxValue = 100,
- step = 1,
- showLimitsValues = false,
- showInput = false,
- disabled = false,
- marks = false,
- onChange,
- onDragEnd,
- labelFormatCallback,
- margin,
- size = "fillParent",
- },
- ref
- ): JSX.Element => {
- const labelId = `label-${useId()}`;
- const [innerValue, setInnerValue] = useState(defaultValue ?? 0);
- const [dragging, setDragging] = useState(false);
- const colorsTheme = useContext(HalstackContext);
- const isFirefox = navigator.userAgent.indexOf("Firefox") !== -1;
-
- const minLabel = useMemo(
- () => (labelFormatCallback ? labelFormatCallback(minValue) : minValue),
- [labelFormatCallback, minValue]
- );
-
- const maxLabel = useMemo(
- () => (labelFormatCallback ? labelFormatCallback(maxValue) : maxValue),
- [labelFormatCallback, maxValue]
- );
-
- const tickMarks = useMemo(() => {
- const numberOfMarks = Math.floor(maxValue / step - minValue / step);
- const range = maxValue - minValue;
- const ticks = [];
-
- if (marks) {
- for (let index = 0; index <= numberOfMarks; index++) {
- ticks.push(
-
- );
- }
- return ticks;
- }
- return null;
- }, [minValue, maxValue, step, value, innerValue]);
-
- const handleSliderChange = (event: ChangeEvent) => {
- const intValue = parseInt(event.target.value, 10);
- if (intValue !== value || intValue !== innerValue) {
- setInnerValue(intValue);
- }
- onChange?.(intValue);
- };
-
- const handleSliderDragging = () => {
- setDragging(true);
- };
-
- const handleSliderOnChangeCommitted = (event: MouseEvent) => {
- const intValue = parseInt((event.target as HTMLInputElement).value, 10);
- if (dragging) {
- setDragging(false);
- onDragEnd?.(intValue);
- }
- };
-
- const handlerInputChange = (event: { value: string; error?: string }) => {
- const intValue = parseInt(event.value, 10);
- if (value == null) {
- if (!Number.isNaN(intValue)) {
- setInnerValue(intValue > maxValue ? maxValue : intValue);
- }
- }
- if (!Number.isNaN(intValue)) {
- onChange?.(intValue > maxValue ? maxValue : intValue);
- }
- };
-
- return (
-
-
-
- {label}
-
- {helperText}
-
- {showLimitsValues && {minLabel} }
-
- = 0 ? value : innerValue}
- min={minValue}
- max={maxValue}
- step={step}
- disabled={disabled}
- aria-labelledby={labelId}
- aria-orientation="horizontal"
- aria-valuemax={maxValue}
- aria-valuemin={minValue}
- aria-valuenow={value != null && value >= 0 ? value : innerValue}
- onChange={handleSliderChange}
- onMouseUp={handleSliderOnChangeCommitted}
- onMouseDown={handleSliderDragging}
- />
- {marks && {tickMarks} }
-
- {showLimitsValues && (
-
- {maxLabel}
-
- )}
- {showInput && (
-
- = 0 ? value.toString() : innerValue.toString()}
- disabled={disabled}
- onChange={handlerInputChange}
- size="fillParent"
- />
-
- )}
-
-
-
- );
- }
-);
-
const sizes = {
medium: "360px",
large: "480px",
@@ -165,7 +21,7 @@ const getChromeStyles = () => `
width: 100%;
margin-right: 4px;`;
-const getFireFoxStyles = () => `
+const getFirefoxStyles = () => `
width: calc(100% - 16px);
margin-right: 3px;`;
@@ -315,7 +171,6 @@ const LimitLabelContainer = styled.span<{
disabled: SliderPropsType["disabled"];
}>`
color: ${(props) => (props.disabled ? props.theme.disabledLimitValuesFontColor : props.theme.limitValuesFontColor)};
-
font-family: ${(props) => props.theme.fontFamily};
font-size: ${(props) => props.theme.limitValuesFontSize};
font-style: ${(props) => props.theme.limitValuesFontStyle};
@@ -345,7 +200,7 @@ const SliderInputContainer = styled.div`
`;
const MarksContainer = styled.div<{ isFirefox: boolean }>`
- ${(props) => (props.isFirefox ? getFireFoxStyles() : getChromeStyles())}
+ ${(props) => (props.isFirefox ? getFirefoxStyles() : getChromeStyles())}
position: absolute;
pointer-events: none;
height: 100%;
@@ -368,9 +223,149 @@ const TickMark = styled.span<{
z-index: ${(props) => props.stepValue != null && `${props.stepPosition <= props.stepValue ? "-1" : "0"}`};
`;
-const StyledTextInput = styled.div`
+const TextInputContainer = styled.div`
margin-left: ${(props) => props.theme.inputMarginLeft};
max-width: 70px;
`;
+const DxcSlider = forwardRef(
+ (
+ {
+ label = "",
+ name = "",
+ defaultValue,
+ value,
+ helperText = "",
+ minValue = 0,
+ maxValue = 100,
+ step = 1,
+ showLimitsValues = false,
+ showInput = false,
+ disabled = false,
+ marks = false,
+ onChange,
+ onDragEnd,
+ labelFormatCallback,
+ margin,
+ size = "fillParent",
+ },
+ ref
+ ): JSX.Element => {
+ const labelId = `label-${useId()}`;
+ const [innerValue, setInnerValue] = useState(defaultValue ?? 0);
+ const [dragging, setDragging] = useState(false);
+ const colorsTheme = useContext(HalstackContext);
+ const isFirefox = navigator.userAgent.indexOf("Firefox") !== -1;
+
+ const minLabel = useMemo(
+ () => (labelFormatCallback ? labelFormatCallback(minValue) : minValue),
+ [labelFormatCallback, minValue]
+ );
+
+ const maxLabel = useMemo(
+ () => (labelFormatCallback ? labelFormatCallback(maxValue) : maxValue),
+ [labelFormatCallback, maxValue]
+ );
+
+ const tickMarks = useMemo(() => {
+ const numberOfMarks = Math.floor(maxValue / step - minValue / step);
+ const range = maxValue - minValue;
+ const ticks = [];
+
+ if (marks) {
+ for (let index = 0; index <= numberOfMarks; index++) {
+ ticks.push(
+
+ );
+ }
+ return ticks;
+ }
+ return null;
+ }, [minValue, maxValue, step, value, innerValue]);
+
+ const handleSliderChange = (event: ChangeEvent) => {
+ const intValue = parseInt(event.target.value, 10);
+ if (intValue !== value || intValue !== innerValue) {
+ setInnerValue(intValue);
+ }
+ onChange?.(intValue);
+ };
+
+ const handleSliderDragging = () => {
+ setDragging(true);
+ };
+
+ const handleSliderOnChangeCommitted = (event: MouseEvent) => {
+ const intValue = parseInt((event.target as HTMLInputElement).value, 10);
+ if (dragging) {
+ setDragging(false);
+ onDragEnd?.(intValue);
+ }
+ };
+
+ const handlerInputChange = (event: { value: string; error?: string }) => {
+ const intValue = parseInt(event.value, 10);
+ if (!Number.isNaN(intValue)) {
+ if (value == null) setInnerValue(intValue > maxValue ? maxValue : intValue);
+ onChange?.(intValue > maxValue ? maxValue : intValue);
+ }
+ };
+
+ return (
+
+
+
+ {label}
+
+ {helperText}
+
+ {showLimitsValues && {minLabel} }
+
+ = 0 ? value : innerValue}
+ min={minValue}
+ max={maxValue}
+ step={step}
+ disabled={disabled}
+ aria-labelledby={labelId}
+ aria-orientation="horizontal"
+ aria-valuemax={maxValue}
+ aria-valuemin={minValue}
+ aria-valuenow={value != null && value >= 0 ? value : innerValue}
+ onChange={handleSliderChange}
+ onMouseUp={handleSliderOnChangeCommitted}
+ onMouseDown={handleSliderDragging}
+ />
+ {marks && {tickMarks} }
+
+ {showLimitsValues && (
+
+ {maxLabel}
+
+ )}
+ {showInput && (
+
+ = 0 ? value.toString() : innerValue.toString()}
+ disabled={disabled}
+ onChange={handlerInputChange}
+ size="fillParent"
+ />
+
+ )}
+
+
+
+ );
+ }
+);
+
export default DxcSlider;
diff --git a/packages/lib/src/switch/Switch.tsx b/packages/lib/src/switch/Switch.tsx
index 1666c59294..544e78f623 100644
--- a/packages/lib/src/switch/Switch.tsx
+++ b/packages/lib/src/switch/Switch.tsx
@@ -8,7 +8,7 @@ import SwitchPropsType, { RefType } from "./types";
const DxcSwitch = forwardRef(
(
{
- defaultChecked,
+ defaultChecked = false,
checked,
value,
label = "",
@@ -25,7 +25,7 @@ const DxcSwitch = forwardRef(
): JSX.Element => {
const switchId = `switch-${useId()}`;
const labelId = `label-${switchId}`;
- const [innerChecked, setInnerChecked] = useState(defaultChecked ?? false);
+ const [innerChecked, setInnerChecked] = useState(defaultChecked);
const colorsTheme = useContext(HalstackContext);
const translatedLabels = useContext(HalstackLanguageContext);
diff --git a/packages/lib/src/text-input/Suggestion.tsx b/packages/lib/src/text-input/Suggestion.tsx
index 297300f92a..f5fcd8c00c 100644
--- a/packages/lib/src/text-input/Suggestion.tsx
+++ b/packages/lib/src/text-input/Suggestion.tsx
@@ -1,19 +1,40 @@
import { memo, useMemo } from "react";
import styled from "styled-components";
import { SuggestionProps } from "./types";
+import { transformSpecialChars } from "./utils";
-const transformSpecialChars = (str: string) => {
- const specialCharsRegex = /[\\*()[\]{}+?/]/;
- let value = str;
- if (specialCharsRegex.test(value)) {
- const regexAsString = specialCharsRegex.toString().split("");
- const uniqueSpecialChars = regexAsString.filter((item, index) => regexAsString.indexOf(item) === index);
- uniqueSpecialChars.forEach((specialChar) => {
- if (str.includes(specialChar)) value = value.replace(specialChar, "\\" + specialChar);
- });
+const SuggestionContainer = styled.li<{
+ visuallyFocused: SuggestionProps["visuallyFocused"];
+}>`
+ display: flex;
+ padding: 0 0.5rem;
+ line-height: 1.715em;
+ cursor: pointer;
+ box-shadow: inset 0 0 0 2px
+ ${(props) => (props.visuallyFocused ? props.theme.focusListOptionBorderColor : "transparent")};
+
+ &:hover {
+ background-color: ${(props) => props.theme.hoverListOptionBackgroundColor};
}
- return value;
-};
+ &:active {
+ background-color: ${(props) => props.theme.activeListOptionBackgroundColor};
+ }
+`;
+
+const StyledSuggestion = styled.span<{
+ visuallyFocused: SuggestionProps["visuallyFocused"];
+ isLast: SuggestionProps["isLast"];
+}>`
+ width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ padding: 0.25rem 0.5rem 0.188rem 0.5rem;
+ ${(props) =>
+ props.isLast || props.visuallyFocused
+ ? `border-bottom: 1px solid transparent`
+ : `border-bottom: 1px solid ${props.theme.listOptionDividerColor}`};
+`;
const Suggestion = ({
id,
@@ -53,37 +74,4 @@ const Suggestion = ({
);
};
-const SuggestionContainer = styled.li<{
- visuallyFocused: SuggestionProps["visuallyFocused"];
-}>`
- display: flex;
- padding: 0 0.5rem;
- line-height: 1.715em;
- cursor: pointer;
- box-shadow: inset 0 0 0 2px
- ${(props) => (props.visuallyFocused ? props.theme.focusListOptionBorderColor : "transparent")};
-
- &:hover {
- background-color: ${(props) => props.theme.hoverListOptionBackgroundColor};
- }
- &:active {
- background-color: ${(props) => props.theme.activeListOptionBackgroundColor};
- }
-`;
-
-const StyledSuggestion = styled.span<{
- visuallyFocused: SuggestionProps["visuallyFocused"];
- isLast: SuggestionProps["isLast"];
-}>`
- width: 100%;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- padding: 0.25rem 0.5rem 0.188rem 0.5rem;
- ${(props) =>
- props.isLast || props.visuallyFocused
- ? `border-bottom: 1px solid transparent`
- : `border-bottom: 1px solid ${props.theme.listOptionDividerColor}`};
-`;
-
export default memo(Suggestion);
diff --git a/packages/lib/src/text-input/Suggestions.tsx b/packages/lib/src/text-input/Suggestions.tsx
index f3fd79e064..d78912df20 100644
--- a/packages/lib/src/text-input/Suggestions.tsx
+++ b/packages/lib/src/text-input/Suggestions.tsx
@@ -5,6 +5,52 @@ import Suggestion from "./Suggestion";
import { SuggestionsProps } from "./types";
import DxcIcon from "../icon/Icon";
+const SuggestionsContainer = styled.ul<{ error: boolean }>`
+ box-sizing: border-box;
+ max-height: 304px;
+ overflow-y: auto;
+ margin: 0;
+ padding: 0.25rem 0;
+ background-color: ${(props) =>
+ props.error ? props.theme.errorListDialogBackgroundColor : props.theme.listDialogBackgroundColor};
+ border: 1px solid
+ ${(props) => (props.error ? props.theme.errorListDialogBorderColor : props.theme.listDialogBorderColor)};
+
+ border-radius: 0.25rem;
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
+ color: ${(props) => props.theme.listOptionFontColor};
+ font-family: ${(props) => props.theme.fontFamily};
+ font-size: ${(props) => props.theme.listOptionFontSize};
+ font-style: ${(props) => props.theme.listOptionFontStyle};
+ font-weight: ${(props) => props.theme.listOptionFontWeight};
+`;
+
+const SuggestionsSystemMessage = styled.span`
+ display: flex;
+ padding: 0.25rem 1rem;
+ color: ${(props) => props.theme.systemMessageFontColor};
+ line-height: 1.715em;
+`;
+
+const SuggestionsErrorIcon = styled.span`
+ display: flex;
+ flex-wrap: wrap;
+ align-content: center;
+ margin-right: 0.5rem;
+ height: 18px;
+ width: 18px;
+ font-size: 18px;
+ color: ${(props) => props.theme.errorIconColor};
+`;
+
+const SuggestionsError = styled.span`
+ display: flex;
+ padding: 0.25rem 1rem;
+ align-items: center;
+ line-height: 1.715em;
+ color: ${(props) => props.theme.errorListDialogFontColor};
+`;
+
const Suggestions = ({
id,
value,
@@ -71,50 +117,4 @@ const Suggestions = ({
);
};
-const SuggestionsContainer = styled.ul<{ error: boolean }>`
- box-sizing: border-box;
- max-height: 304px;
- overflow-y: auto;
- margin: 0;
- padding: 0.25rem 0;
- background-color: ${(props) =>
- props.error ? props.theme.errorListDialogBackgroundColor : props.theme.listDialogBackgroundColor};
- border: 1px solid
- ${(props) => (props.error ? props.theme.errorListDialogBorderColor : props.theme.listDialogBorderColor)};
-
- border-radius: 0.25rem;
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
- color: ${(props) => props.theme.listOptionFontColor};
- font-family: ${(props) => props.theme.fontFamily};
- font-size: ${(props) => props.theme.listOptionFontSize};
- font-style: ${(props) => props.theme.listOptionFontStyle};
- font-weight: ${(props) => props.theme.listOptionFontWeight};
-`;
-
-const SuggestionsSystemMessage = styled.span`
- display: flex;
- padding: 0.25rem 1rem;
- color: ${(props) => props.theme.systemMessageFontColor};
- line-height: 1.715em;
-`;
-
-const SuggestionsErrorIcon = styled.span`
- display: flex;
- flex-wrap: wrap;
- align-content: center;
- margin-right: 0.5rem;
- height: 18px;
- width: 18px;
- font-size: 18px;
- color: ${(props) => props.theme.errorIconColor};
-`;
-
-const SuggestionsError = styled.span`
- display: flex;
- padding: 0.25rem 1rem;
- align-items: center;
- line-height: 1.715em;
- color: ${(props) => props.theme.errorListDialogFontColor};
-`;
-
export default memo(Suggestions);
diff --git a/packages/lib/src/text-input/TextInput.tsx b/packages/lib/src/text-input/TextInput.tsx
index 56957e107a..99720796f5 100644
--- a/packages/lib/src/text-input/TextInput.tsx
+++ b/packages/lib/src/text-input/TextInput.tsx
@@ -5,7 +5,6 @@ import {
forwardRef,
KeyboardEvent,
MouseEvent,
- ReactNode,
useContext,
useEffect,
useId,
@@ -15,7 +14,6 @@ import {
} from "react";
import styled, { ThemeProvider } from "styled-components";
import DxcActionIcon from "../action-icon/ActionIcon";
-import { getMargin } from "../common/utils";
import { spaces } from "../common/variables";
import DxcFlex from "../flex/Flex";
import DxcIcon from "../icon/Icon";
@@ -24,59 +22,182 @@ import HalstackContext, { HalstackLanguageContext } from "../HalstackContext";
import useWidth from "../utils/useWidth";
import Suggestions from "./Suggestions";
import TextInputPropsType, { AutosuggestWrapperProps, RefType } from "./types";
+import {
+ calculateWidth,
+ hasSuggestions,
+ isLengthIncorrect,
+ isNumberIncorrect,
+ isRequired,
+ makeCancelable,
+ patternMismatch,
+} from "./utils";
-const sizes = {
- small: "240px",
- medium: "360px",
- large: "480px",
- fillParent: "100%",
-};
+const TextInputContainer = styled.div<{
+ margin: TextInputPropsType["margin"];
+ size: TextInputPropsType["size"];
+}>`
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ width: ${(props) => calculateWidth(props.margin, props.size)};
+ ${(props) => props.size !== "fillParent" && `min-width:${calculateWidth(props.margin, props.size)}`};
+ margin: ${(props) => (props.margin && typeof props.margin !== "object" ? spaces[props.margin] : "0px")};
+ margin-top: ${(props) =>
+ props.margin && typeof props.margin === "object" && props.margin.top ? spaces[props.margin.top] : ""};
+ margin-right: ${(props) =>
+ props.margin && typeof props.margin === "object" && props.margin.right ? spaces[props.margin.right] : ""};
+ margin-bottom: ${(props) =>
+ props.margin && typeof props.margin === "object" && props.margin.bottom ? spaces[props.margin.bottom] : ""};
+ margin-left: ${(props) =>
+ props.margin && typeof props.margin === "object" && props.margin.left ? spaces[props.margin.left] : ""};
+ font-family: ${(props) => props.theme.fontFamily};
+`;
-const AutosuggestWrapper = ({ condition, wrapper, children }: AutosuggestWrapperProps): JSX.Element => (
- <>{condition ? wrapper(children) : children}>
-);
+const Label = styled.label<{
+ disabled: TextInputPropsType["disabled"];
+ hasHelperText: boolean;
+}>`
+ color: ${(props) => (props.disabled ? props.theme.disabledLabelFontColor : props.theme.labelFontColor)};
+ font-size: ${(props) => props.theme.labelFontSize};
+ font-style: ${(props) => props.theme.labelFontStyle};
+ font-weight: ${(props) => props.theme.labelFontWeight};
+ line-height: ${(props) => props.theme.labelLineHeight};
+ ${(props) => !props.hasHelperText && `margin-bottom: 0.25rem`}
+`;
-const calculateWidth = (margin: TextInputPropsType["margin"], size: TextInputPropsType["size"]) =>
- size === "fillParent"
- ? `calc(${sizes[size]} - ${getMargin(margin, "left")} - ${getMargin(margin, "right")})`
- : size && sizes[size];
-
-const makeCancelable = (promise: Promise) => {
- let hasCanceled_ = false;
- const wrappedPromise = new Promise((resolve, reject) => {
- promise.then(
- (val) => (hasCanceled_ ? reject(Error("Is canceled")) : resolve(val)),
- (promiseError) => (hasCanceled_ ? reject(Error("Is canceled")) : reject(promiseError))
- );
- });
- return {
- promise: wrappedPromise,
- cancel() {
- hasCanceled_ = true;
- },
- };
-};
+const OptionalLabel = styled.span`
+ font-weight: ${(props) => props.theme.optionalLabelFontWeight};
+`;
+
+const HelperText = styled.span<{ disabled: TextInputPropsType["disabled"] }>`
+ color: ${(props) => (props.disabled ? props.theme.disabledHelperTextFontColor : props.theme.helperTextFontColor)};
+ font-size: ${(props) => props.theme.helperTextFontSize};
+ font-style: ${(props) => props.theme.helperTextFontStyle};
+ font-weight: ${(props) => props.theme.helperTextFontWeight};
+ line-height: ${(props) => props.theme.helperTextLineHeight};
+ margin-bottom: 0.25rem;
+`;
+
+const InputContainer = styled.div<{
+ disabled: TextInputPropsType["disabled"];
+ readOnly: TextInputPropsType["readOnly"];
+ error: boolean;
+}>`
+ position: relative;
+ display: flex;
+ align-items: center;
+ height: calc(2.5rem - 2px);
+ padding: 0 0.5rem;
+
+ ${(props) => props.disabled && `background-color: ${props.theme.disabledContainerFillColor};`}
+ box-shadow: 0 0 0 2px transparent;
+ border-radius: 4px;
+ border: 1px solid
+ ${(props) =>
+ props.disabled
+ ? props.theme.disabledBorderColor
+ : props.readOnly
+ ? props.theme.readOnlyBorderColor
+ : props.theme.enabledBorderColor};
+ ${(props) =>
+ props.error &&
+ !props.disabled &&
+ `border-color: transparent;
+ box-shadow: 0 0 0 2px ${props.theme.errorBorderColor};
+ `}
+ ${(props) =>
+ !props.disabled
+ ? `
+ &:hover {
+ border-color: ${
+ props.error
+ ? "transparent"
+ : props.readOnly
+ ? props.theme.hoverReadOnlyBorderColor
+ : props.theme.hoverBorderColor
+ };
+ ${props.error ? `box-shadow: 0 0 0 2px ${props.theme.hoverErrorBorderColor};` : ""}
+ }
+ &:focus-within {
+ border-color: transparent;
+ box-shadow: 0 0 0 2px ${props.theme.focusBorderColor};
+ }
+ `
+ : "cursor: not-allowed;"};
+`;
-const hasSuggestions = (suggestions: TextInputPropsType["suggestions"]) =>
- typeof suggestions === "function" || (suggestions ? suggestions.length > 0 : false);
+const Input = styled.input`
+ height: calc(2.5rem - 2px);
+ width: 100%;
+ background: none;
+ border: none;
+ outline: none;
+ padding: 0 0.5rem;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: ${(props) => (props.disabled ? props.theme.disabledValueFontColor : props.theme.valueFontColor)};
+ font-family: ${(props) => props.theme.fontFamily};
+ font-size: ${(props) => props.theme.valueFontSize};
+ font-style: ${(props) => props.theme.valueFontStyle};
+ font-weight: ${(props) => props.theme.valueFontWeight};
+ line-height: 1.5em;
+ ${(props) => props.disabled && `cursor: not-allowed;`}
+
+ ::placeholder {
+ color: ${(props) => (props.disabled ? props.theme.disabledPlaceholderFontColor : props.theme.placeholderFontColor)};
+ }
+`;
+
+const Prefix = styled.span<{ disabled: TextInputPropsType["disabled"] }>`
+ height: 1.5rem;
+ margin-left: 0.25rem;
+ padding-right: ${(props) => props.theme.prefixDividerPaddingRight};
+ ${(props) => {
+ const color = props.disabled ? props.theme.disabledPrefixColor : props.theme.prefixColor;
+ return `color: ${color}; border-right: ${props.theme.prefixDividerBorderWidth} ${props.theme.prefixDividerBorderStyle} ${color};`;
+ }};
+ font-size: 1rem;
+ line-height: 1.5rem;
+ pointer-events: none;
+`;
-const isRequired = (value: string, optional: boolean) => value === "" && !optional;
+const Suffix = styled.span<{ disabled: TextInputPropsType["disabled"] }>`
+ height: 1.5rem;
+ margin: 0 0.25rem;
+ padding-left: ${(props) => props.theme.suffixDividerPaddingLeft};
+ ${(props) => {
+ const color = props.disabled ? props.theme.disabledSuffixColor : props.theme.suffixColor;
+ return `color: ${color}; border-left: ${props.theme.suffixDividerBorderWidth} ${props.theme.suffixDividerBorderStyle} ${color};`;
+ }};
+ font-size: 1rem;
+ line-height: 1.5rem;
+ pointer-events: none;
+`;
-const isLengthIncorrect = (
- value: string,
- minLength: TextInputPropsType["minLength"],
- maxLength: TextInputPropsType["maxLength"]
-) =>
- value != null && ((minLength != null && value.length < minLength) || (maxLength != null && value.length > maxLength));
+const ErrorIcon = styled.span`
+ display: flex;
+ flex-wrap: wrap;
+ align-content: center;
+ padding: 3px;
+ height: 18px;
+ width: 18px;
+ font-size: 18px;
+ color: ${(props) => props.theme.errorIconColor};
+`;
-const isNumberIncorrect = (
- value: number,
- minNumber: TextInputPropsType["minLength"],
- maxNumber: TextInputPropsType["maxLength"]
-) => (minNumber != null && value < minNumber) || (maxNumber != null && value > maxNumber);
+const ErrorMessageContainer = styled.span`
+ min-height: 1.5em;
+ color: ${(props) => props.theme.errorMessageColor};
+ font-size: 0.75rem;
+ font-weight: 400;
+ line-height: 1.5em;
+ margin-top: 0.25rem;
+`;
-const patternMismatch = (pattern: TextInputPropsType["pattern"], value: string) =>
- pattern != null && !new RegExp(pattern).test(value);
+const AutosuggestWrapper = ({ condition, wrapper, children }: AutosuggestWrapperProps): JSX.Element => (
+ <>{condition ? wrapper(children) : children}>
+);
const DxcTextInput = forwardRef(
(
@@ -112,6 +233,14 @@ const DxcTextInput = forwardRef(
const autosuggestId = `suggestions-${inputId}`;
const errorId = `error-${inputId}`;
+ const colorsTheme = useContext(HalstackContext);
+ const translatedLabels = useContext(HalstackLanguageContext);
+ const numberInputContext = useContext(NumberInputContext);
+
+ const inputRef = useRef(null);
+ const inputContainerRef = useRef(null);
+ const actionRef = useRef(null);
+
const [innerValue, setInnerValue] = useState(defaultValue);
const [isOpen, changeIsOpen] = useState(false);
const [isSearching, changeIsSearching] = useState(false);
@@ -119,14 +248,7 @@ const DxcTextInput = forwardRef(
const [filteredSuggestions, changeFilteredSuggestions] = useState([]);
const [visualFocusIndex, changeVisualFocusIndex] = useState(-1);
- const inputRef = useRef(null);
- const inputContainerRef = useRef(null);
- const actionRef = useRef(null);
-
const width = useWidth(inputContainerRef.current);
- const colorsTheme = useContext(HalstackContext);
- const translatedLabels = useContext(HalstackLanguageContext);
- const numberInputContext = useContext(NumberInputContext);
const getNumberErrorMessage = (checkedValue: number) =>
numberInputContext?.minNumber != null && checkedValue < numberInputContext?.minNumber
@@ -281,10 +403,10 @@ const DxcTextInput = forwardRef(
} else {
openSuggestions();
if (!isAutosuggestError && !isSearching && filteredSuggestions.length > 0) {
- changeVisualFocusIndex((visualFocusedSuggIndex: number) =>
- visualFocusedSuggIndex < filteredSuggestions.length - 1
- ? visualFocusedSuggIndex + 1
- : visualFocusedSuggIndex === filteredSuggestions.length - 1
+ changeVisualFocusIndex((visualFocusedSuggestionIndex: number) =>
+ visualFocusedSuggestionIndex < filteredSuggestions.length - 1
+ ? visualFocusedSuggestionIndex + 1
+ : visualFocusedSuggestionIndex === filteredSuggestions.length - 1
? 0
: -1
);
@@ -299,12 +421,12 @@ const DxcTextInput = forwardRef(
} else {
openSuggestions();
if (!isAutosuggestError && !isSearching && filteredSuggestions.length > 0) {
- changeVisualFocusIndex((visualFocusedSuggIndex) =>
- visualFocusedSuggIndex === 0 || visualFocusedSuggIndex === -1
+ changeVisualFocusIndex((visualFocusedSuggestionIndex) =>
+ visualFocusedSuggestionIndex === 0 || visualFocusedSuggestionIndex === -1
? filteredSuggestions.length > 0
? filteredSuggestions.length - 1
: (suggestions?.length ?? 0) - 1
- : visualFocusedSuggIndex - 1
+ : visualFocusedSuggestionIndex - 1
);
}
}
@@ -579,167 +701,4 @@ const DxcTextInput = forwardRef(
}
);
-const TextInputContainer = styled.div<{
- margin: TextInputPropsType["margin"];
- size: TextInputPropsType["size"];
-}>`
- box-sizing: border-box;
- display: flex;
- flex-direction: column;
- width: ${(props) => calculateWidth(props.margin, props.size)};
- ${(props) => props.size !== "fillParent" && `min-width:${calculateWidth(props.margin, props.size)}`};
- margin: ${(props) => (props.margin && typeof props.margin !== "object" ? spaces[props.margin] : "0px")};
- margin-top: ${(props) =>
- props.margin && typeof props.margin === "object" && props.margin.top ? spaces[props.margin.top] : ""};
- margin-right: ${(props) =>
- props.margin && typeof props.margin === "object" && props.margin.right ? spaces[props.margin.right] : ""};
- margin-bottom: ${(props) =>
- props.margin && typeof props.margin === "object" && props.margin.bottom ? spaces[props.margin.bottom] : ""};
- margin-left: ${(props) =>
- props.margin && typeof props.margin === "object" && props.margin.left ? spaces[props.margin.left] : ""};
- font-family: ${(props) => props.theme.fontFamily};
-`;
-
-const Label = styled.label<{
- disabled: TextInputPropsType["disabled"];
- hasHelperText: boolean;
-}>`
- color: ${(props) => (props.disabled ? props.theme.disabledLabelFontColor : props.theme.labelFontColor)};
- font-size: ${(props) => props.theme.labelFontSize};
- font-style: ${(props) => props.theme.labelFontStyle};
- font-weight: ${(props) => props.theme.labelFontWeight};
- line-height: ${(props) => props.theme.labelLineHeight};
- ${(props) => !props.hasHelperText && `margin-bottom: 0.25rem`}
-`;
-
-const OptionalLabel = styled.span`
- font-weight: ${(props) => props.theme.optionalLabelFontWeight};
-`;
-
-const HelperText = styled.span<{ disabled: TextInputPropsType["disabled"] }>`
- color: ${(props) => (props.disabled ? props.theme.disabledHelperTextFontColor : props.theme.helperTextFontColor)};
- font-size: ${(props) => props.theme.helperTextFontSize};
- font-style: ${(props) => props.theme.helperTextFontStyle};
- font-weight: ${(props) => props.theme.helperTextFontWeight};
- line-height: ${(props) => props.theme.helperTextLineHeight};
- margin-bottom: 0.25rem;
-`;
-
-const InputContainer = styled.div<{
- disabled: TextInputPropsType["disabled"];
- readOnly: TextInputPropsType["readOnly"];
- error: boolean;
-}>`
- position: relative;
- display: flex;
- align-items: center;
- height: calc(2.5rem - 2px);
- padding: 0 0.5rem;
-
- ${(props) => props.disabled && `background-color: ${props.theme.disabledContainerFillColor};`}
- box-shadow: 0 0 0 2px transparent;
- border-radius: 4px;
- border: 1px solid
- ${(props) =>
- props.disabled
- ? props.theme.disabledBorderColor
- : props.readOnly
- ? props.theme.readOnlyBorderColor
- : props.theme.enabledBorderColor};
- ${(props) =>
- props.error &&
- !props.disabled &&
- `border-color: transparent;
- box-shadow: 0 0 0 2px ${props.theme.errorBorderColor};
- `}
- ${(props) =>
- !props.disabled
- ? `
- &:hover {
- border-color: ${
- props.error
- ? "transparent"
- : props.readOnly
- ? props.theme.hoverReadOnlyBorderColor
- : props.theme.hoverBorderColor
- };
- ${props.error ? `box-shadow: 0 0 0 2px ${props.theme.hoverErrorBorderColor};` : ""}
- }
- &:focus-within {
- border-color: transparent;
- box-shadow: 0 0 0 2px ${props.theme.focusBorderColor};
- }
- `
- : "cursor: not-allowed;"};
-`;
-
-const Input = styled.input`
- height: calc(2.5rem - 2px);
- width: 100%;
- background: none;
- border: none;
- outline: none;
- padding: 0 0.5rem;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- color: ${(props) => (props.disabled ? props.theme.disabledValueFontColor : props.theme.valueFontColor)};
- font-family: ${(props) => props.theme.fontFamily};
- font-size: ${(props) => props.theme.valueFontSize};
- font-style: ${(props) => props.theme.valueFontStyle};
- font-weight: ${(props) => props.theme.valueFontWeight};
- line-height: 1.5em;
- ${(props) => props.disabled && `cursor: not-allowed;`}
-
- ::placeholder {
- color: ${(props) => (props.disabled ? props.theme.disabledPlaceholderFontColor : props.theme.placeholderFontColor)};
- }
-`;
-
-const Prefix = styled.span<{ disabled: TextInputPropsType["disabled"] }>`
- height: 1.5rem;
- margin-left: 0.25rem;
- padding-right: ${(props) => props.theme.prefixDividerPaddingRight};
- ${(props) => {
- const color = props.disabled ? props.theme.disabledPrefixColor : props.theme.prefixColor;
- return `color: ${color}; border-right: ${props.theme.prefixDividerBorderWidth} ${props.theme.prefixDividerBorderStyle} ${color};`;
- }};
- font-size: 1rem;
- line-height: 1.5rem;
- pointer-events: none;
-`;
-
-const Suffix = styled.span<{ disabled: TextInputPropsType["disabled"] }>`
- height: 1.5rem;
- margin: 0 0.25rem;
- padding-left: ${(props) => props.theme.suffixDividerPaddingLeft};
- ${(props) => {
- const color = props.disabled ? props.theme.disabledSuffixColor : props.theme.suffixColor;
- return `color: ${color}; border-left: ${props.theme.suffixDividerBorderWidth} ${props.theme.suffixDividerBorderStyle} ${color};`;
- }};
- font-size: 1rem;
- line-height: 1.5rem;
- pointer-events: none;
-`;
-
-const ErrorIcon = styled.span`
- display: flex;
- flex-wrap: wrap;
- align-content: center;
- padding: 3px;
- height: 18px;
- width: 18px;
- font-size: 18px;
- color: ${(props) => props.theme.errorIconColor};
-`;
-
-const ErrorMessageContainer = styled.span`
- min-height: 1.5em;
- color: ${(props) => props.theme.errorMessageColor};
- font-size: 0.75rem;
- font-weight: 400;
- line-height: 1.5em;
- margin-top: 0.25rem;
-`;
-
export default DxcTextInput;
diff --git a/packages/lib/src/text-input/utils.ts b/packages/lib/src/text-input/utils.ts
new file mode 100644
index 0000000000..3e7bb102f7
--- /dev/null
+++ b/packages/lib/src/text-input/utils.ts
@@ -0,0 +1,64 @@
+import TextInputPropsType from "./types";
+import { getMargin } from "../common/utils";
+
+const sizes = {
+ small: "240px",
+ medium: "360px",
+ large: "480px",
+ fillParent: "100%",
+};
+
+export const calculateWidth = (margin: TextInputPropsType["margin"], size: TextInputPropsType["size"]) =>
+ size === "fillParent"
+ ? `calc(${sizes[size]} - ${getMargin(margin, "left")} - ${getMargin(margin, "right")})`
+ : size && sizes[size];
+
+export const makeCancelable = (promise: Promise) => {
+ let hasCanceled_ = false;
+ const wrappedPromise = new Promise((resolve, reject) => {
+ promise.then(
+ (val) => (hasCanceled_ ? reject(Error("Is canceled")) : resolve(val)),
+ (promiseError) => (hasCanceled_ ? reject(Error("Is canceled")) : reject(promiseError))
+ );
+ });
+ return {
+ promise: wrappedPromise,
+ cancel() {
+ hasCanceled_ = true;
+ },
+ };
+};
+
+export const hasSuggestions = (suggestions: TextInputPropsType["suggestions"]) =>
+ typeof suggestions === "function" || (suggestions ? suggestions.length > 0 : false);
+
+export const isRequired = (value: string, optional: boolean) => value === "" && !optional;
+
+export const isLengthIncorrect = (
+ value: string,
+ minLength: TextInputPropsType["minLength"],
+ maxLength: TextInputPropsType["maxLength"]
+) =>
+ value != null && ((minLength != null && value.length < minLength) || (maxLength != null && value.length > maxLength));
+
+export const isNumberIncorrect = (
+ value: number,
+ minNumber: TextInputPropsType["minLength"],
+ maxNumber: TextInputPropsType["maxLength"]
+) => (minNumber != null && value < minNumber) || (maxNumber != null && value > maxNumber);
+
+export const patternMismatch = (pattern: TextInputPropsType["pattern"], value: string) =>
+ pattern != null && !new RegExp(pattern).test(value);
+
+export const transformSpecialChars = (str: string) => {
+ const specialCharsRegex = /[\\*()[\]{}+?/]/;
+ let value = str;
+ if (specialCharsRegex.test(value)) {
+ const regexAsString = specialCharsRegex.toString().split("");
+ const uniqueSpecialChars = regexAsString.filter((item, index) => regexAsString.indexOf(item) === index);
+ uniqueSpecialChars.forEach((specialChar) => {
+ if (str.includes(specialChar)) value = value.replace(specialChar, "\\" + specialChar);
+ });
+ }
+ return value;
+};
\ No newline at end of file
diff --git a/packages/lib/src/toast/types.ts b/packages/lib/src/toast/types.ts
index ec3d7c0837..73f84bce0e 100644
--- a/packages/lib/src/toast/types.ts
+++ b/packages/lib/src/toast/types.ts
@@ -2,22 +2,45 @@ import { ReactNode } from "react";
import { SVG } from "../common/utils";
type Action = {
+ /**
+ * The icon of the action. It can be a string with the name of the icon or an SVG component.
+ */
icon?: string | SVG;
+ /**
+ * The label of the action.
+ */
label: string;
+ /**
+ * The function that will be executed when the user clicks on the action button.
+ */
onClick: () => void;
};
type CommonProps = {
+ /**
+ * Tertiary button which performs a custom action, specified by the user.
+ */
action?: Action;
+ /**
+ * Message to be displayed as a toast.
+ */
message: string;
};
type DefaultToast = CommonProps & {
+ /**
+ * Material Symbol name or SVG element as the icon that will be placed next to the panel label.
+ * When using Material Symbols, replace spaces with underscores.
+ * By default they are outlined if you want it to be filled prefix the symbol name with "filled_".
+ */
icon?: string | SVG;
};
type LoadingToast = CommonProps & {
loading: boolean;
};
type SemanticToast = CommonProps & {
+ /**
+ * Flag that allows to hide the semantic icon of the toast.
+ */
hideSemanticIcon?: boolean;
};
type ToastType = DefaultToast | LoadingToast | SemanticToast;
@@ -42,7 +65,17 @@ type ToastPropsType = {
hideSemanticIcon?: boolean;
};
-type ToastsQueuePropsType = { duration?: number; children: ReactNode };
+type ToastsQueuePropsType = {
+ /**
+ * Duration in milliseconds before a toast automatically hides itself.
+ * The range goes from 3000ms to 5000ms, any other value will not be taken into consideration.
+ */
+ duration?: number;
+ /**
+ * Tree of components from which the useToast hook can be triggered.
+ */
+ children: ReactNode;
+};
export default ToastPropsType;
export type {
diff --git a/packages/lib/src/tooltip/Tooltip.tsx b/packages/lib/src/tooltip/Tooltip.tsx
index 6afebd6aa2..ea1fdab172 100644
--- a/packages/lib/src/tooltip/Tooltip.tsx
+++ b/packages/lib/src/tooltip/Tooltip.tsx
@@ -1,7 +1,7 @@
import styled from "styled-components";
import CoreTokens from "../common/coreTokens";
import TooltipPropsType, { TooltipWrapperProps } from "./types";
-import { createContext, useContext } from "react";
+import { useContext } from "react";
import { Root, Trigger, Portal, Arrow, Content } from "@radix-ui/react-tooltip";
import { Provider } from "@radix-ui/react-tooltip";
import { TooltipContext } from "./TooltipContext";
diff --git a/packages/lib/src/wizard/Icons.tsx b/packages/lib/src/wizard/Icons.tsx
new file mode 100644
index 0000000000..34398df76c
--- /dev/null
+++ b/packages/lib/src/wizard/Icons.tsx
@@ -0,0 +1,39 @@
+const icons = {
+ valid: (
+
+
+
+
+
+ ),
+ invalid: (
+
+
+
+
+
+ ),
+};
+
+export default icons;
diff --git a/packages/lib/src/wizard/Wizard.tsx b/packages/lib/src/wizard/Wizard.tsx
index ce0a397a06..5e50e4dece 100644
--- a/packages/lib/src/wizard/Wizard.tsx
+++ b/packages/lib/src/wizard/Wizard.tsx
@@ -1,123 +1,10 @@
-import { useContext, useState } from "react";
+import { useContext, useMemo, useState } from "react";
import styled, { ThemeProvider } from "styled-components";
import { spaces } from "../common/variables";
import DxcIcon from "../icon/Icon";
import HalstackContext from "../HalstackContext";
import WizardPropsType, { StepProps } from "./types";
-
-const icons = {
- validIcon: (
-
-
-
-
-
- ),
- invalidIcon: (
-
-
-
-
-
- ),
-};
-
-const DxcWizard = ({
- mode = "horizontal",
- defaultCurrentStep,
- currentStep,
- onStepClick,
- steps,
- margin,
- tabIndex = 0,
-}: WizardPropsType): JSX.Element => {
- const [innerCurrent, setInnerCurrentStep] = useState(currentStep ?? defaultCurrentStep ?? 0);
- const renderedCurrent = currentStep ?? innerCurrent;
- const colorsTheme = useContext(HalstackContext);
-
- const handleStepClick = (newValue: number) => {
- setInnerCurrentStep(newValue);
- onStepClick?.(newValue);
- };
-
- return (
-
-
- {steps.map((step, i) => (
-
- {
- handleStepClick(i);
- }}
- disabled={step.disabled}
- mode={mode}
- first={i === 0}
- last={i === steps.length - 1}
- aria-current={renderedCurrent === i ? "step" : "false"}
- tabIndex={tabIndex}
- >
-
-
- {step.icon ? (
- typeof step.icon === "string" ? (
-
- ) : (
- step.icon
- )
- ) : (
- {i + 1}
- )}
-
- {step.valid !== undefined &&
- (step.valid ? (
- {icons.validIcon}
- ) : (
- {icons.invalidIcon}
- ))}
-
- {(step.label || step.description) && (
-
- {step.label && (
-
- {step.label}
-
- )}
- {step.description && (
-
- {step.description}
-
- )}
-
- )}
-
- {i === steps.length - 1 ? "" : }
-
- ))}
-
-
- );
-};
+import icons from "./Icons";
const StepsContainer = styled.div<{
mode: WizardPropsType["mode"];
@@ -159,6 +46,7 @@ const Step = styled.button<{
display: flex;
justify-content: flex-start;
align-items: center;
+ gap: 0.75rem;
border: none;
border-radius: 0.25rem;
background: inherit;
@@ -279,10 +167,6 @@ const ValidityIconContainer = styled.div`
left: 22.5px;
`;
-const InfoContainer = styled.div`
- margin-left: 12px;
-`;
-
const Label = styled.p<{
current: boolean;
visited: boolean;
@@ -342,4 +226,78 @@ const StepSeparator = styled.div<{ mode: WizardPropsType["mode"] }>`
flex-grow: 1;
`;
+const DxcWizard = ({
+ mode = "horizontal",
+ defaultCurrentStep = 0,
+ currentStep,
+ onStepClick,
+ steps,
+ margin,
+ tabIndex = 0,
+}: WizardPropsType): JSX.Element => {
+ const colorsTheme = useContext(HalstackContext);
+ const [innerCurrent, setInnerCurrentStep] = useState(defaultCurrentStep);
+
+ const renderedCurrent = useMemo(() => currentStep ?? innerCurrent, [currentStep, innerCurrent]);
+
+ const handleStepClick = (newValue: number) => {
+ setInnerCurrentStep(newValue);
+ onStepClick?.(newValue);
+ };
+
+ return (
+
+
+ {steps.map((step, i) => (
+
+ {
+ handleStepClick(i);
+ }}
+ disabled={step.disabled}
+ mode={mode}
+ first={i === 0}
+ last={i === steps.length - 1}
+ aria-current={renderedCurrent === i ? "step" : "false"}
+ tabIndex={tabIndex}
+ >
+
+
+ {step.icon ? (
+ typeof step.icon === "string" ? (
+
+ ) : (
+ step.icon
+ )
+ ) : (
+ {i + 1}
+ )}
+
+ {step.valid != null && (
+ {step.valid ? icons.valid : icons.invalid}
+ )}
+
+ {(step.label || step.description) && (
+
+ {step.label && (
+
+ {step.label}
+
+ )}
+ {step.description && (
+
+ {step.description}
+
+ )}
+
+ )}
+
+ {i === steps.length - 1 ? "" : }
+
+ ))}
+
+
+ );
+};
+
export default DxcWizard;