Skip to content

Commit

Permalink
fix(Toggle): better dark and light modes (#418)
Browse files Browse the repository at this point in the history
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced a `focusMode` property for the Toggle component, allowing
users to customize the focus ring color according to their preference or
system settings.
- **Documentation**
- Updated the Toggle component stories to showcase the new `focusMode`
feature and other enhancements like `labelHidden` and `label` props.
- **Tests**
- Enhanced Toggle component tests to include checks for classes based on
toggle state and mode.
- **Refactor**
- Improved Toggle component logic and utilities for handling focus
styles and color classes more efficiently.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • Loading branch information
aversini authored Mar 15, 2024
1 parent 22ed3fe commit e3231cf
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 40 deletions.
3 changes: 2 additions & 1 deletion packages/documentation/nodemon.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"src/**/*.*",
"../ui-components/src/**/*.*",
"../ui-components/lib/**/*.*",
"../ui-styles/src/**/*.*"
"../ui-styles/src/**/*.*",
"../ui-form/src/**/*.*"
]
}
19 changes: 9 additions & 10 deletions packages/documentation/src/Form/Toggle.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,27 @@ export default {
};

export const Basic: Story<any> = (args) => {
const [checked, setChecked] = useState(false);
const [checked, setChecked] = useState(true);

return (
<div className="flex flex-wrap gap-2">
<Toggle
onChange={setChecked}
checked={checked}
label="Toggle"
name="Toggle"
{...args}
/>
<Toggle onChange={setChecked} checked={checked} name="Toggle" {...args} />
</div>
);
};
Basic.args = {
checked: false,
disabled: false,
mode: "system",
focusMode: "system",
labelHidden: false,
label: "Toggle",
};
Basic.argTypes = {
mode: {
options: ["dark", "light", "system", "alt-system"],
control: { type: "radio" },
},
focusMode: {
options: ["dark", "light", "system", "alt-system"],
control: { type: "radio" },
},
};
8 changes: 7 additions & 1 deletion packages/ui-form/src/components/Toggle/Toggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@ export const Toggle = ({
labelHidden = false,
name,
mode = "system",
focusMode = "system",
spacing,
}: ToggleProps) => {
const toggleClasses = getToggleClasses({ mode, labelHidden, spacing });
const toggleClasses = getToggleClasses({
mode,
focusMode,
labelHidden,
spacing,
});
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
onChange?.(e.target.checked);
};
Expand Down
11 changes: 10 additions & 1 deletion packages/ui-form/src/components/Toggle/ToggleTypes.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { SpacingProps } from "@versini/ui-private/dist/utilities";

export type ToggleProps = {
checked: boolean;
/**
* The label to use for the component.
*/
Expand All @@ -15,6 +14,16 @@ export type ToggleProps = {
* @param checked whether or not the component is checked
*/
onChange: (checked: boolean) => void;
/**
* Whether or not the component is checked.
* @default false
*/
checked?: boolean;
/**
* The type of focus for the Button. This will change the color
* of the focus ring around the Button.
*/
focusMode?: "dark" | "light" | "system" | "alt-system";
/**
* Whether or not to render the label.
* @default false
Expand Down
83 changes: 78 additions & 5 deletions packages/ui-form/src/components/Toggle/__tests__/Toggle.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { fireEvent, render, screen } from "@testing-library/react";

import { expectToHaveClasses } from "../../../../../../configuration/tests-helpers";
import { TOGGLE_CLASSNAME } from "../../../common/constants";
import { Toggle } from "../..";

describe("Toggle (exceptions)", () => {
Expand All @@ -22,11 +23,47 @@ describe("Toggle modifiers", () => {
onChange={() => {}}
/>,
);
const node = screen.getByText("toto");
expectToHaveClasses(node, ["ml-3", "text-sm", "text-copy-lighter"]);
const label = screen.getByText("toto");
const input = screen.getByRole("checkbox");
const toggle = input.nextElementSibling;

expectToHaveClasses(label, ["ml-3", "text-sm", "text-copy-lighter"]);
if (toggle) {
expectToHaveClasses(toggle, [
TOGGLE_CLASSNAME,
"after:absolute",
"after:bg-surface-light",
"after:border-surface-light",
"after:border",
"after:content-['']",
"after:h-5",
"after:left-[2px]",
"after:rounded-full",
"after:top-[2px]",
"after:transition-all",
"after:w-5",
"bg-surface-darker",
"border-border-light",
"dark:after:bg-surface-medium",
"dark:after:border-surface-medium",
"dark:peer-focus:outline-focus-light",
"h-6",
"peer-checked:after:bg-white",
"peer-checked:after:border-white",
"peer-checked:after:translate-x-full",
"peer-checked:bg-[#5bc236]",
"peer-focus:outline-2",
"peer-focus:outline-focus-dark",
"peer-focus:outline-offset-2",
"peer-focus:outline",
"peer",
"rounded-full",
"w-11",
]);
}
});

it("should render a light Toggle ", async () => {
it("should render a light Toggle", async () => {
render(
<Toggle
mode="light"
Expand All @@ -36,8 +73,44 @@ describe("Toggle modifiers", () => {
onChange={() => {}}
/>,
);
const node = screen.getByText("toto");
expectToHaveClasses(node, ["ml-3", "text-sm", "text-copy-dark"]);

const label = screen.getByText("toto");
const input = screen.getByRole("checkbox");
const toggle = input.nextElementSibling;
expectToHaveClasses(label, ["ml-3", "text-sm", "text-copy-dark"]);
if (toggle) {
expectToHaveClasses(toggle, [
TOGGLE_CLASSNAME,
"peer",
"h-6",
"w-11",
"rounded-full",
"border-border-dark",
"bg-surface-medium",
"peer-focus:outline",
"peer-focus:outline-2",
"peer-focus:outline-offset-2",
"peer-focus:outline-focus-dark",
"dark:peer-focus:outline-focus-light",
"after:left-[2px]",
"after:top-[2px]",
"after:border",
"after:border-surface-light",
"dark:after:border-surface-medium",
"after:bg-surface-light",
"dark:after:bg-surface-medium",
"after:absolute",
"after:h-5",
"after:w-5",
"after:rounded-full",
"after:transition-all",
"after:content-['']",
"peer-checked:bg-[#5bc236]",
"peer-checked:after:translate-x-full",
"peer-checked:after:bg-white",
"peer-checked:after:border-white",
]);
}
});

it("should render a Toggle with no label", async () => {
Expand Down
63 changes: 41 additions & 22 deletions packages/ui-form/src/components/Toggle/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,38 @@ import clsx from "clsx";
import { TOGGLE_CLASSNAME } from "../../common/constants";

const getToggleBaseClasses = () => {
return clsx(TOGGLE_CLASSNAME, "peer h-6 w-11 rounded-full");
return clsx(TOGGLE_CLASSNAME, "peer", "h-6", "w-11", "rounded-full");
};

const getToggleKnobClasses = () => {
const getToggleKnobFocusClasses = ({
focusMode,
}: {
focusMode?: "dark" | "light" | "system" | "alt-system";
}) => {
return clsx(
"after:absolute",
"after:h-5",
"after:w-5",
"after:rounded-full",
"after:transition-all",
"after:content-['']",
"peer-focus:outline-none",
"peer-focus:ring-2",
"peer-focus:ring-white",
"peer-focus:outline",
"peer-focus:outline-2",
"peer-focus:outline-offset-2",
{
"peer-focus:outline-focus-dark": focusMode === "dark",
"peer-focus:outline-focus-light": focusMode === "light",

"peer-focus:outline-focus-light dark:peer-focus:outline-focus-dark":
focusMode === "alt-system",

"peer-focus:outline-focus-dark dark:peer-focus:outline-focus-light":
focusMode === "system",
},
);
};

const getToggleKnobOnClasses = () => {
return clsx(
"peer-checked:bg-[#5bc236]",

"peer-checked:after:translate-x-full",
"peer-checked:after:bg-white",

"peer-checked:after:border-white",
);
};
Expand All @@ -34,12 +45,22 @@ const getToggleKnobOffClasses = () => {
"after:left-[2px]",
"after:top-[2px]",
"after:border",
"after:border-white",
"after:bg-white",
"after:border-surface-light dark:after:border-surface-medium",
"after:bg-surface-light dark:after:bg-surface-medium",
"after:absolute",
"after:h-5",
"after:w-5",
"after:rounded-full",
"after:transition-all",
"after:content-['']",
);
};

const getToggleColorClasses = ({ mode }: { mode: string }) => {
const getToggleColorClasses = ({
mode,
}: {
mode: "dark" | "light" | "system" | "alt-system";
}) => {
return clsx({
"border-border-dark bg-surface-medium": mode === "light",
"border-border-light bg-surface-darker": mode === "dark",
Expand All @@ -55,7 +76,7 @@ const getLabelClasses = ({
labelHidden,
}: {
labelHidden: boolean;
mode: string;
mode: "dark" | "light" | "system" | "alt-system";
}) => {
return labelHidden
? "sr-only"
Expand All @@ -67,32 +88,30 @@ const getLabelClasses = ({
});
};

const getInputClasses = () => {
return "peer sr-only";
};

const getWrapperClasses = ({ spacing }: SpacingProps) => {
return clsx("relative flex cursor-pointer items-center", getSpacing(spacing));
};

export const getToggleClasses = ({
mode,
focusMode,
labelHidden,
spacing,
}: {
focusMode: "dark" | "light" | "system" | "alt-system";
labelHidden: boolean;
mode: string;
mode: "dark" | "light" | "system" | "alt-system";
} & SpacingProps) => {
return {
toggle: clsx(
getToggleBaseClasses(),
getToggleColorClasses({ mode }),
getToggleKnobClasses(),
getToggleKnobFocusClasses({ focusMode }),
getToggleKnobOffClasses(),
getToggleKnobOnClasses(),
),
label: getLabelClasses({ mode, labelHidden }),
input: getInputClasses(),
input: "peer sr-only",
wrapper: getWrapperClasses({ spacing }),
};
};

0 comments on commit e3231cf

Please sign in to comment.