Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Switch #1309

Merged
merged 15 commits into from Oct 7, 2022
26 changes: 18 additions & 8 deletions lib/src/switch/Switch.test.js
Expand Up @@ -78,17 +78,27 @@ describe("Switch component tests", () => {
expect(onChange.mock.calls[1][0]).toBe(false);
});
test("Renders with correct initial value and initial state when it is uncontrolled", () => {
const { getByRole } = render(<DxcSwitch label="Default label" defaultChecked value="test-defaultChecked" />);
const inputEl = getByRole("switch");
expect(inputEl.checked).toBe(true);
const component = render(
<DxcSwitch label="Default label" defaultChecked value="test-defaultChecked" name="test" />
);
const switchEl = component.getByRole("switch");
const inputEl = component.container.querySelector(`input[name="test"]`);
expect(inputEl.value).toBe("test-defaultChecked");
expect(inputEl.getAttribute("aria-checked")).toBe("true");
expect(switchEl.getAttribute("aria-checked")).toBe("true");
});
test("Renders with correct aria attributes", () => {
const { getByRole, getByText } = render(<DxcSwitch label="Default label" />);
const inputEl = getByRole("switch");
const { getByText, getByRole } = render(<DxcSwitch label="Default label" />);
const switchEl = getByRole("switch");
const label = getByText("Default label");
expect(inputEl.getAttribute("aria-labelledby")).toBe(label.id);
expect(inputEl.getAttribute("aria-checked")).toBe("false");
expect(switchEl.getAttribute("aria-labelledby")).toBe(label.id);
expect(switchEl.getAttribute("aria-checked")).toBe("false");
});
test("Renders disabled switch correctly", () => {
const { getByText, getByRole } = render(<DxcSwitch label="Default label" disabled />);
const switchEl = getByRole("switch");
const label = getByText("Default label");
expect(switchEl.getAttribute("aria-labelledby")).toBe(label.id);
expect(switchEl.getAttribute("aria-checked")).toBe("false");
expect(switchEl.getAttribute("aria-disabled")).toBe("true");
});
});
68 changes: 30 additions & 38 deletions lib/src/switch/Switch.tsx
Expand Up @@ -25,7 +25,6 @@ const DxcSwitch = ({
const [switchId] = useState(`switch-${uuidv4()}`);
const labelId = `label-${switchId}`;
const [innerChecked, setInnerChecked] = useState(defaultChecked ?? false);
const [hasLabel] = useState((label !== "" && label !== null && label !== undefined) ?? false);

const colorsTheme = useTheme();
const translatedLabels = useTranslatedLabels();
Expand Down Expand Up @@ -63,61 +62,64 @@ const DxcSwitch = ({

return (
<ThemeProvider theme={colorsTheme.switch}>
<SwitchContainer margin={margin} size={size} onKeyDown={handleOnKeyDown}>
{labelPosition === "before" && hasLabel && (
<SwitchContainer
margin={margin}
size={size}
onKeyDown={handleOnKeyDown}
onClick={!disabled ? handlerSwitchChange : undefined}
disabled={disabled}
>
{labelPosition === "before" && label && (
<LabelContainer
id={labelId}
labelPosition={labelPosition}
onClick={!disabled ? handlerSwitchChange : undefined}
disabled={disabled}
backgroundType={backgroundType}
hasLabel={hasLabel}
label={label}
>
{label} {optional && <>{translatedLabels.formFields.optionalLabel}</>}
</LabelContainer>
)}
<SwitchBase
labelPosition={labelPosition}
hasLabel={hasLabel}
htmlFor={labelId}
onClick={disabled === true ? () => {} : handlerSwitchChange}
>
<SwitchBase htmlFor={labelId}>
aidamag marked this conversation as resolved.
Show resolved Hide resolved
<SwitchInput
GomezIvann marked this conversation as resolved.
Show resolved Hide resolved
type="checkbox"
role="switch"
name={name}
id={labelId}
disabled={disabled}
aria-hidden={true}
value={value}
aria-labelledby={labelId}
aria-label={hasLabel ? label : undefined}
aria-checked={checked ?? innerChecked}
defaultChecked={defaultChecked ?? undefined}
tabIndex={-1}
disabled={disabled}
readOnly
></SwitchInput>
{disabled ? (
<DisabledSwitchTrack
role="switch"
backgroundType={backgroundType}
data-checked={checked ?? (innerChecked ? innerChecked : undefined)}
aria-checked={checked ?? innerChecked}
aria-labelledby={labelId}
aria-disabled={true}
tabIndex={-1}
/>
) : (
<SwitchTrack
role="switch"
backgroundType={backgroundType}
data-checked={checked ?? (innerChecked ? innerChecked : undefined)}
aria-checked={checked ?? innerChecked}
aria-disabled={false}
aria-labelledby={labelId}
tabIndex={tabIndex}
ref={refTrack}
/>
)}
</SwitchBase>
{labelPosition === "after" && hasLabel && (
{labelPosition === "after" && label && (
<LabelContainer
id={labelId}
labelPosition={labelPosition}
onClick={!disabled ? handlerSwitchChange : undefined}
disabled={disabled}
backgroundType={backgroundType}
hasLabel={hasLabel}
label={label}
>
{optional && <>{translatedLabels.formFields.optionalLabel}</>} {label}
</LabelContainer>
Expand All @@ -143,13 +145,15 @@ const calculateWidth = (margin, size) =>
type SwitchContainerProps = {
margin: Margin | Space;
size: "small" | "medium" | "large" | "fillParent" | "fitContent";
disabled: boolean;
};

const SwitchContainer = styled.div<SwitchContainerProps>`
display: inline-flex;
align-items: center;
width: ${(props) => calculateWidth(props.margin, props.size)};
height: 40px;
cursor: ${(props) => (props.disabled === true ? "not-allowed" : "pointer")};

margin: ${(props) => (props.margin && typeof props.margin !== "object" ? spaces[props.margin] : "0px")};
margin-top: ${(props) =>
Expand All @@ -166,7 +170,7 @@ type LabelProps = {
backgroundType: "dark" | "light";
labelPosition: "after" | "before";
disabled: boolean;
hasLabel: boolean;
label: string;
};

const LabelContainer = styled.span<LabelProps>`
Expand All @@ -186,10 +190,9 @@ const LabelContainer = styled.span<LabelProps>`
font-size: ${(props) => props.theme.labelFontSize};
font-style: ${(props) => (props.disabled ? props.theme.disabledLabelFontStyle : props.theme.labelFontStyle)};
font-weight: ${(props) => props.theme.labelFontWeight};
cursor: ${(props) => (props.disabled === true ? "not-allowed" : "pointer")};

${(props) =>
!props.hasLabel
!props.label
? "margin: 0px;"
: props.labelPosition === "after"
? `margin-left: ${props.theme.spaceBetweenLabelSwitch};`
Expand All @@ -198,25 +201,16 @@ const LabelContainer = styled.span<LabelProps>`
${(props) => props.labelPosition === "before" && "order: -1"}
`;

type SwitchBaseProps = {
labelPosition: "after" | "before";
hasLabel: boolean;
};

const SwitchBase = styled.label<SwitchBaseProps>`
const SwitchBase = styled.label`
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
margin: ${(props) =>
!props.hasLabel ? "0px 4px" : props.labelPosition === "before" ? "0 4px 0 12px" : "0 12px 0 4px"};
margin: 0px 12px;
`;

const SwitchInput = styled.input`
opacity: 0;
width: 0;
height: 0;
margin: 0px;
display: none;
`;

type SwitchTrackProps = {
Expand All @@ -228,7 +222,6 @@ const SwitchTrack = styled.span<SwitchTrackProps>`
width: ${(props) => props.theme.trackWidth};
height: ${(props) => props.theme.trackHeight};
position: relative;
transition: transform 0.2s ease;

&:focus-visible {
outline: none;
Expand All @@ -245,7 +238,6 @@ const SwitchTrack = styled.span<SwitchTrackProps>`
::before {
content: "";
transform: initial;
transition: transform 0.2s ease;
position: absolute;
width: ${(props) => props.theme.thumbWidth};
height: ${(props) => props.theme.thumbHeight};
Expand Down