Skip to content

Commit f9b518b

Browse files
Merge branch 'deriv-com:main' into yaswanth/FEQ-1913_Add_hoverstyle_prop_to_button
2 parents 2e870af + 57a90e0 commit f9b518b

File tree

11 files changed

+303
-34
lines changed

11 files changed

+303
-34
lines changed

README.md

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
# Deriv Ui Library (@deriv-com/ui)
2-
<img alt="NPM Version" src="https://img.shields.io/npm/v/@deriv-com/ui"> <img alt="NPM Downloads" src="https://img.shields.io/npm/dw/@deriv-com/ui"> [![TypeScript](https://badges.frapsoft.com/typescript/code/typescript.png?v=101)](https://github.com/ellerbrock/typescript-badges/) [![MIT Licence](https://badges.frapsoft.com/os/mit/mit.png?v=102)](https://opensource.org/licenses/mit-license.php)
1+
# Deriv Ui Library (@deriv-com/ui)
2+
<img alt="NPM Version" src="https://img.shields.io/npm/v/@deriv-com/ui"> <img alt="NPM Downloads" src="https://img.shields.io/npm/dw/@deriv-com/ui"> <img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="@deriv-com/ui is released under the MIT license." /> [![Coverage Status](https://coveralls.io/repos/github/deriv-com/ui/badge.svg)](https://coveralls.io/github/deriv-com/ui)
33

44

55
## Overview
6-
This is a UI component library made for ReactJS that conforms to the [Deriv Design System](https://zeroheight.com/36313d3c8/p/439a5c-deriv-design-system). Explore our [Storybook](https://deriv-ui.pages.dev/) for for comprehensive development documentation .
6+
This is a UI component library made for ReactJS that conforms to the [Deriv Design System](https://zeroheight.com/36313d3c8/p/439a5c-deriv-design-system). Explore our [Storybook](https://deriv-ui.pages.dev/) for for comprehensive development documentation .
77

88
Also available on [npm](https://www.npmjs.com/package/@deriv-com/ui).
99

@@ -38,3 +38,10 @@ npm run storybook
3838

3939
## Contributing
4040
Contributions to the @deriv-com/ui library are warmly encouraged. If you have suggestions for enhancements or encounter a bug, please feel free to open an issue or submit a pull request (ensure you fork it first).
41+
42+
<a height="15" href = "https://github.com/deriv-com/ui/contributors">
43+
<img src = "https://contrib.rocks/image?repo=deriv-com/ui&anon=0&columns=20"/>
44+
</a>
45+
46+
47+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import React from "react";
2+
import { render } from "@testing-library/react";
3+
import { InlineMessage } from "..";
4+
5+
describe("InlineMessage Component", () => {
6+
it("renders children correctly", () => {
7+
const { getByText } = render(<InlineMessage>Test Message</InlineMessage>);
8+
expect(getByText("Test Message")).toBeInTheDocument();
9+
});
10+
11+
it("renders with specified variant", () => {
12+
const { container } = render(
13+
<InlineMessage variant="error">Error Message</InlineMessage>
14+
);
15+
expect(container.firstChild).toHaveClass(
16+
"deriv-inline-message__error--filled"
17+
);
18+
});
19+
20+
it("renders with specified icon", () => {
21+
const { container } = render(
22+
<InlineMessage icon={<span>Icon</span>}>Message with Icon</InlineMessage>
23+
);
24+
expect(container.querySelector(".deriv-inline-message__icon")).toBeInTheDocument();
25+
});
26+
27+
it("renders with specified type", () => {
28+
const { container } = render(
29+
<InlineMessage variant="warning" type="outlined">Outlined Warning Message</InlineMessage>
30+
);
31+
expect(container.firstChild).toHaveClass(
32+
"deriv-inline-message__warning--outlined"
33+
);
34+
});
35+
36+
it("renders with custom class name", () => {
37+
const { container } = render(
38+
<InlineMessage className="custom-class">Custom Message</InlineMessage>
39+
);
40+
expect(container.firstChild).toHaveClass("custom-class");
41+
});
42+
43+
it("should position the icon at the top", () => {
44+
const { container } = render(
45+
<InlineMessage iconPosition="top" variant="info">
46+
Test Message
47+
</InlineMessage>
48+
);
49+
const iconElement = container.querySelector(".deriv-inline-message__icon--top")
50+
expect(iconElement).toBeInTheDocument();
51+
});
52+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React from "react";
2+
import { render, screen } from "@testing-library/react";
3+
import { PageLayout } from "..";
4+
import * as hooks from "../../../hooks"
5+
6+
// Mocking the useDevice hook
7+
jest.mock("../../../hooks", () => ({
8+
useDevice: jest.fn().mockReturnValue({isMobile:false}),
9+
}));
10+
11+
describe("PageLayout Component", () => {
12+
it("renders children correctly", () => {
13+
render(
14+
<PageLayout>
15+
<div>Content</div>
16+
</PageLayout>
17+
);
18+
const content =screen.getByText("Content")
19+
expect(content).toBeInTheDocument();
20+
});
21+
22+
it("renders sidebar when provided and not on mobile", () => {
23+
const sidebar = <div>Sidebar</div>;
24+
render(<PageLayout sidebar={sidebar} />);
25+
const sidebarContent =screen.getByText("Sidebar")
26+
expect(sidebarContent).toBeInTheDocument();
27+
});
28+
29+
it("does not render sidebar on mobile", () => {
30+
jest.spyOn(hooks,'useDevice').mockImplementation(()=>({isMobile:true, isDesktop:false, isTablet:false}))
31+
const sidebar = <div>Sidebar</div>;
32+
render(<PageLayout sidebar={sidebar} />);
33+
const sidebarContent =screen.queryByText("Sidebar")
34+
expect(sidebarContent).not.toBeInTheDocument()
35+
});
36+
37+
it("does not render sidebar when not provided", () => {
38+
render(<PageLayout />);
39+
const sidebarContent=screen.queryByTestId("sidebar")
40+
expect(sidebarContent).toBeNull();
41+
});
42+
});

src/components/PageLayout/index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from "react";
2-
import { useDevice } from "../../hooks/useDevice";
2+
import { useDevice } from "../../hooks";
33
import "./PageLayout.scss";
44

55
type PageLayoutProps = {

src/components/SideNote/ChevronRightIcon.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ export const ChevronRightIcon = () => (
99
<g id="icon">
1010
<path
1111
id="icon_2"
12-
fill-rule="evenodd"
13-
clip-rule="evenodd"
12+
fillRule="evenodd"
13+
clipRule="evenodd"
1414
d="M3.85983 1.60983C4.00628 1.46339 4.24372 1.46339 4.39017 1.60983L8.51516 5.73484C8.66161 5.88128 8.66161 6.11872 8.51516 6.26516L4.39017 10.3902C4.24372 10.5366 4.00628 10.5366 3.85983 10.3902C3.71339 10.2437 3.71339 10.0063 3.85983 9.85983L7.71967 6L3.85983 2.14017C3.71339 1.99372 3.71339 1.75628 3.85983 1.60983Z"
1515
fill="#FF444F"
1616
/>
+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
2+
import React from "react";
3+
import { render, fireEvent, screen } from "@testing-library/react";
4+
import { Tab, Tabs } from "..";
5+
6+
describe("Tabs Component", () => {
7+
it("renders Tabs component with primary variant by default", () => {
8+
const { container } = render(
9+
<Tabs>
10+
<Tab title="Tab 1">Tab 1 Content</Tab>
11+
<Tab title="Tab 2">Tab 2 Content</Tab>
12+
</Tabs>
13+
);
14+
15+
const tabsComponent = container.querySelector(".derivs-primary-tabs")
16+
const tabTitle = screen.getByText("Tab 1")
17+
const tab1Content = screen.getByText("Tab 1 Content")
18+
expect(tabTitle).toBeInTheDocument();
19+
expect(tabsComponent).toBeInTheDocument();
20+
expect(tab1Content).toBeInTheDocument();
21+
expect(tabsComponent).toHaveClass("derivs-primary-tabs");
22+
});
23+
24+
it("changes active tab when clicked and checks active tab content showing", () => {
25+
render(
26+
<Tabs activeTab="Tab 1">
27+
<Tab title="Tab 1">Tab 1 Content</Tab>
28+
<Tab title="Tab 2">Tab 2 Content</Tab>
29+
</Tabs>
30+
);
31+
const tab1 = screen.getByText("Tab 1 Content")
32+
expect(tab1).toBeInTheDocument();
33+
const tab2 = screen.getByText("Tab 2");
34+
fireEvent.click(tab2);
35+
const activeButton = screen.getAllByRole("button")[1];
36+
const tabContent = screen.getByText("Tab 2 Content")
37+
expect(tabContent).toBeInTheDocument();
38+
expect(activeButton).toHaveClass("derivs-primary-tabs__btn--active");
39+
});
40+
41+
it("should render tabs with correct variant", () => {
42+
const { container } = render(
43+
<Tabs variant="secondary">
44+
<Tab title="Tab 1">Tab 1 Content</Tab>
45+
<Tab title="Tab 2">Tab 2 Content</Tab>
46+
</Tabs>
47+
);
48+
const tabsComponent = container.querySelector(".derivs-secondary-tabs")
49+
expect(tabsComponent).toBeInTheDocument();
50+
});
51+
52+
it("should render tabs with correct title font size", () => {
53+
const { container } = render(
54+
<Tabs TitleFontSize="lg">
55+
<Tab title="Tab 1">Tab 1 Content</Tab>
56+
<Tab title="Tab 2">Tab 2 Content</Tab>
57+
</Tabs>
58+
);
59+
60+
const tabsComponent = container.querySelector(".derivs-primary-tabs")
61+
62+
expect(tabsComponent).toHaveStyle("font-size: lg;");
63+
});
64+
65+
it("should call onChange handler when a tab is clicked", () => {
66+
const mockOnChange = jest.fn();
67+
const { getByText } = render(
68+
<Tabs onChange={mockOnChange}>
69+
<Tab title="Tab 1">Tab 1 Content</Tab>
70+
<Tab title="Tab 2">Tab 2 Content</Tab>
71+
</Tabs>
72+
);
73+
const tabTitle = getByText("Tab 1");
74+
fireEvent.click(tabTitle);
75+
expect(mockOnChange).toHaveBeenCalled();
76+
});
77+
78+
it("applies custom class to wrapper", () => {
79+
const { container } = render(
80+
<Tabs wrapperClassName="custom-wrapper">
81+
<Tab title="Tab 1">Tab 1 Content</Tab>
82+
<Tab title="Tab 2">Tab 2 Content</Tab>
83+
</Tabs>
84+
);
85+
const tabsWrapper = container.querySelector(".custom-wrapper");
86+
expect(tabsWrapper).toBeInTheDocument();
87+
});
88+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import React from "react";
2+
import { render } from "@testing-library/react";
3+
import { ToggleSwitch } from "..";
4+
import userEvent from "@testing-library/user-event";
5+
6+
describe("ToggleSwitch Component", () => {
7+
it("should renders with correct value passed as prop", () => {
8+
const { getByRole, rerender } = render(<ToggleSwitch value={true} />);
9+
const toggleSwitch = getByRole("checkbox");
10+
expect(toggleSwitch).toBeChecked();
11+
rerender(<ToggleSwitch value={false} />);
12+
expect(toggleSwitch).not.toBeChecked();
13+
});
14+
15+
it("should be set to false when no value prop is provided", () => {
16+
const { getByRole } = render(<ToggleSwitch/>);
17+
const toggleSwitch = getByRole("checkbox");
18+
expect(toggleSwitch).not.toBeChecked();
19+
});
20+
it("should update the value when user clicks on it", async () => {
21+
const { getByRole } = render(<ToggleSwitch/>);
22+
const toggleSwitch = getByRole("checkbox");
23+
expect(toggleSwitch).not.toBeChecked();
24+
await userEvent.click(toggleSwitch);
25+
expect(toggleSwitch).toBeChecked();
26+
});
27+
28+
it("should call the onChange prop when user clicks on it", async () => {
29+
const mockedOnChange = jest.fn();
30+
const { getByRole } = render(<ToggleSwitch onChange={mockedOnChange} />);
31+
const toggleSwitch = getByRole("checkbox");
32+
await userEvent.click(toggleSwitch);
33+
expect(mockedOnChange).toHaveBeenCalled();
34+
});
35+
36+
37+
it("should apply wrapperClassName and className correctly", () => {
38+
const { container } = render(
39+
<ToggleSwitch
40+
wrapperClassName="wrapper-class"
41+
className="custom-class"
42+
/>
43+
);
44+
const wrapper = container.querySelector(".deriv-toggle-switch.wrapper-class");
45+
expect(wrapper).toBeInTheDocument();
46+
const input = wrapper?.querySelector("input");
47+
expect(input).toHaveClass("custom-class");
48+
});
49+
50+
it("applies wrapperStyle and style correctly", () => {
51+
const { container } = render(
52+
<ToggleSwitch
53+
wrapperStyle={{ backgroundColor: "red" }}
54+
style={{ fontSize: "16px" }}
55+
/>
56+
);
57+
const input = container.querySelector(".deriv-toggle-switch input");
58+
expect(input).toHaveStyle({ fontSize: "16px" });
59+
const label = input?.parentElement;
60+
expect(label).toHaveStyle({ backgroundColor: "red" });
61+
});
62+
});

src/components/ToggleSwitch/index.tsx

+42-20
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import React, { ChangeEvent, forwardRef } from "react";
1+
import React, { ChangeEvent, forwardRef, useEffect, useState } from "react";
22
import clsx from "clsx";
33
import { Input } from "../Input";
44
import "./ToggleSwitch.scss";
55

66
interface ToggleSwitchProps {
7-
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
8-
value: boolean;
7+
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
8+
value?: boolean;
99
wrapperClassName?: React.ComponentProps<typeof Input>["wrapperClassName"];
1010
className?: React.ComponentProps<typeof Input>["className"];
1111
wrapperStyle?: React.CSSProperties;
@@ -14,22 +14,44 @@ interface ToggleSwitchProps {
1414

1515
export const ToggleSwitch = forwardRef<HTMLInputElement, ToggleSwitchProps>(
1616
(
17-
{ onChange, value, wrapperClassName, className, wrapperStyle, style },
17+
{
18+
onChange,
19+
value = false,
20+
wrapperClassName,
21+
className,
22+
wrapperStyle,
23+
style,
24+
},
1825
ref,
19-
) => (
20-
<label
21-
className={clsx("deriv-toggle-switch", wrapperClassName)}
22-
style={wrapperStyle}
23-
>
24-
<input
25-
checked={value}
26-
onChange={onChange}
27-
ref={ref}
28-
type="checkbox"
29-
className={clsx(className)}
30-
style={style}
31-
/>
32-
<span className="deriv-toggle-switch__slider" />
33-
</label>
34-
),
26+
) => {
27+
const [isChecked, setIsChecked] = useState<boolean>(value);
28+
29+
useEffect(() => {
30+
if (value !== undefined) {
31+
setIsChecked(value);
32+
}
33+
}, [value]);
34+
35+
const onClickHandler = (event: ChangeEvent<HTMLInputElement>) => {
36+
setIsChecked(!isChecked);
37+
onChange?.(event);
38+
};
39+
40+
return (
41+
<label
42+
className={clsx("deriv-toggle-switch", wrapperClassName)}
43+
style={wrapperStyle}
44+
>
45+
<input
46+
checked={isChecked}
47+
onChange={onClickHandler}
48+
ref={ref}
49+
type="checkbox"
50+
className={clsx(className)}
51+
style={style}
52+
/>
53+
<span className="deriv-toggle-switch__slider" />
54+
</label>
55+
);
56+
},
3557
);

src/hooks/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export {useDevice} from './useDevice'
2+
export {useOnClickOutside} from './useOnClickOutside'

src/main.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,5 @@ export { Text } from "./components/Text";
1919
export { TextArea } from "./components/TextArea";
2020
export { ToggleSwitch } from "./components/ToggleSwitch";
2121
export { Tooltip } from "./components/Tooltip";
22-
export { useDevice } from "./hooks/useDevice";
23-
export { useOnClickOutside } from "./hooks/useOnClickOutside";
22+
export { useDevice,useOnClickOutside } from "./hooks";
2423
export { VerticalTab, VerticalTabItems } from "./components/VerticalTab";

stories/ToggleSwitch.stories.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { ToggleSwitch } from "../src/components/ToggleSwitch";
55
const meta = {
66
title: "Components/ToggleSwitch",
77
component: ToggleSwitch,
8+
tags: ["autodocs"],
89
args: {
910
onChange: action("ToggleSwitch changed"),
1011
value: false,
@@ -22,9 +23,3 @@ export const Default: Story = {
2223
},
2324
};
2425

25-
export const Checked: Story = {
26-
args: {
27-
...Default.args,
28-
value: true,
29-
},
30-
};

0 commit comments

Comments
 (0)