Skip to content

Commit

Permalink
Merge pull request #40 from deriv-com/meenu-tab-component
Browse files Browse the repository at this point in the history
  • Loading branch information
prince-deriv committed Apr 15, 2024
2 parents bacea1a + 2c5f9a1 commit 0412595
Show file tree
Hide file tree
Showing 19 changed files with 538 additions and 1 deletion.
2 changes: 1 addition & 1 deletion lib/components/Typography/text/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ export const Text = ({
as = "p",
size = "md",
italic = false,
color,
underlined = false,
bold = false,
color,
...rest
}: TypographyProps) => {
const decoration = getTextDecoration(italic, underlined);
Expand Down
10 changes: 10 additions & 0 deletions lib/components/tabs/container/container.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { render, screen } from "@testing-library/react";
import Tab from "..";

describe("<Tab.Container/>", () => {
it("should render correctly", () => {
render(<Tab.Container id="tab">Container</Tab.Container>);
const container = screen.getByText("Container");
expect(container).toBeInTheDocument();
});
});
35 changes: 35 additions & 0 deletions lib/components/tabs/container/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { createContext, useState } from 'react'
import { TabProps } from '../types'

export type TabContextType = TabProps & {
handleToggle?: (idx: number) => void
activeTab?: number
}

export const TabContext = createContext<TabContextType>({
activeTab: 0,
})

export const TabContainer = ({
children,
id = 'tab-default-id',
size = 'md',
iconPosition = 'left',
className,
}: TabProps) => {
const [activeTab, setActiveTab] = useState(0)

const handleToggle = (index: number) => {
setActiveTab(index)
}

return (
<TabContext.Provider
value={{ activeTab, handleToggle, id, size, iconPosition }}
>
<div id={id} className={className}>
{children}
</div>
</TabContext.Provider>
)
}
42 changes: 42 additions & 0 deletions lib/components/tabs/content/content.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {
RenderOptions,
RenderResult,
render as rtlRender,
screen,
} from "@testing-library/react";
import Tab from "..";
import { TabContextType } from "../container";

const render = (
ui: React.ReactElement,
options?: RenderOptions & {
wrapperProps?: TabContextType;
},
): RenderResult => {
const { wrapperProps, ...renderOptions } = options || {};
return rtlRender(ui, {
wrapper: (props: TabContextType) => (
<Tab.Container {...props} {...wrapperProps} />
),
...renderOptions,
});
};

describe("<Tab.Container/>", () => {
it("should render only active element", () => {
render(
<Tab.Content>
<div>Panel 1</div>
<div>Panel 2</div>
</Tab.Content>,
{
wrapperProps: {
activeTab: 0,
id: "tab",
},
},
);
const content = screen.getByText("Panel 1");
expect(content).toBeInTheDocument();
});
});
13 changes: 13 additions & 0 deletions lib/components/tabs/content/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Children, ComponentProps, useContext } from 'react'
import { TabContext } from '../container'

type ContentProps = ComponentProps<'div'>

export const TabContent = ({ children, className }: ContentProps) => {
const { activeTab } = useContext(TabContext)
const childArr = Children.toArray(children)
const activeChild = childArr.find((_el, i) => i === activeTab)
return <div className={className}>{activeChild}</div>
}

TabContent.displayName = 'TabContent'
26 changes: 26 additions & 0 deletions lib/components/tabs/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ReactNode } from 'react'
import { TabContainer } from './container'
import { TabList } from './list'
import { TabTrigger } from './trigger'
import { TabContent } from './content'
import { TabPanel } from './panel'

type TabType = {
Container: typeof TabContainer
List: typeof TabList
Trigger: typeof TabTrigger
Content: typeof TabContent
Panel: typeof TabPanel
}

export const Tab: TabType = ({ children }: { children: ReactNode }) => {
return <>{children}</>
}

Tab.Container = TabContainer
Tab.List = TabList
Tab.Trigger = TabTrigger
Tab.Content = TabContent
Tab.Panel = TabPanel

export default Tab
19 changes: 19 additions & 0 deletions lib/components/tabs/list/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ComponentProps } from 'react'
import clsx from 'clsx'
import './list.scss'

type TabListProps = ComponentProps<'div'>

export const TabList = ({ children, className }: TabListProps) => {
return (
<div className={clsx('tab-list--container', className)}>
<div
className="tab-list--item"
role="tablist"
aria-orientation="horizontal"
>
{children}
</div>
</div>
)
}
19 changes: 19 additions & 0 deletions lib/components/tabs/list/list.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.tab-list {
&--container {
position: relative;
display: flex;
justify-content: center;
}

&--item {
display: flex;
overflow-x: auto;
overscroll-behavior-x: contain;
scrollbar-width: none;
-ms-overflow-style: none;

&::-webkit-scrollbar {
display: none;
}
}
}
10 changes: 10 additions & 0 deletions lib/components/tabs/list/list.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { render, screen } from "@testing-library/react";
import Tab from "..";

describe("<Tab.List/>", () => {
it("should render correctly", () => {
render(<Tab.List>List</Tab.List>);
const list = screen.getByText("List");
expect(list).toBeInTheDocument();
});
});
34 changes: 34 additions & 0 deletions lib/components/tabs/mocks/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { ComponentProps } from "react";
import Tab from "../index";
import { Text } from "../../Typography";

const MockTab = ({
size,
iconPosition,
...props
}: ComponentProps<typeof Tab.Container> &
ComponentProps<typeof Tab.Trigger>) => {
return (
<Tab.Container id="test" size={size} iconPosition={iconPosition}>
<Tab.List>
<Tab.Trigger {...props}>Forex</Tab.Trigger>
<Tab.Trigger {...props}>Derived Indices</Tab.Trigger>
<Tab.Trigger disabled={true} {...props}>
Stocks
</Tab.Trigger>
</Tab.List>
<Tab.Content>
<Tab.Panel>
<Text>Forex Tab</Text>
</Tab.Panel>
<Tab.Panel>
<Text>Derived indices Tab</Text>
</Tab.Panel>
<Tab.Panel>
<Text>Stocks Tab</Text>
</Tab.Panel>
</Tab.Content>
</Tab.Container>
);
};
export default MockTab;
21 changes: 21 additions & 0 deletions lib/components/tabs/panel/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ComponentProps, useContext } from 'react'
import { TabContext } from '../container'

type PaneProps = ComponentProps<'div'>

export const TabPanel = ({ children, className }: PaneProps) => {
const { activeTab, id } = useContext(TabContext)

return (
<div
role="tabpanel"
id={`${id}-panel`}
aria-labelledby={`${id}-trigger-${activeTab}`}
className={className}
>
{children}
</div>
)
}

TabPanel.displayName = 'TabPanel'
16 changes: 16 additions & 0 deletions lib/components/tabs/panel/panel.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { render, screen } from "@testing-library/react";
import Tab from "..";

describe("<Tab.Panel/>", () => {
it("should render correctly", () => {
render(<Tab.Panel>Panel</Tab.Panel>);
const panel = screen.getByText("Panel");
expect(panel).toBeInTheDocument();
});

it("should render correctly with props", () => {
render(<Tab.Panel className="px-500">Panel</Tab.Panel>);
const panel = screen.getByText("Panel");
expect(panel).toHaveClass("px-500");
});
});
46 changes: 46 additions & 0 deletions lib/components/tabs/tabs.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
@import '@quill/spacing.scss/';
@import '@quill/color.semantic.scss';
@import '@quill/border.scss';
@import '@quill/spacing.scss';
@import '@quill/size.scss';
@import '@quill/opacity.scss';
@import '@quill/color.opacity.scss';

.tabs {
border-bottom: var(--core-borderRadius-100) solid var(--core-color-opacity-black-200);
display: inline-flex;

&:active:not(:disabled) {
border-bottom-color: var(--core-borderRadius-100) solid var(--core-color-opacity-black-300);
}

&:hover:not(:disabled) {
border-bottom-color: var(--semantic-color-typography-default);
}

&:disabled {
opacity: var(--core-opacity-600);
}

&__selected-tab {
border-bottom: var(--core-borderRadius-100) solid var(--semantic-color-typography-prominent);
}

&__variants {
&--size {
&-sm {
font-size: var(--core-font-size-75);
padding: var(--core-spacing-400) var(--core-spacing-200);
gap: var(--core-spacing-200);
}

&-md {
font-size: var(--core-font-size-100);
padding: var(--core-spacing-800) var(--core-spacing-600);
gap: var(--core-spacing-400);
}

}
}

}
46 changes: 46 additions & 0 deletions lib/components/tabs/tabs.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { Meta } from "@storybook/react";
import {} from "@deriv/quill-icons/LabelPaired";
import MockTab from "./mocks/index";
import { StandalonePlaceholderRegularIcon } from "@deriv/quill-icons";
import { ComponentProps } from "react";

const meta = {
title: "Components/Tabs",
component: MockTab,
tags: ["autodocs"],
parameters: {
layout: "centered",
},
args: {
size: "sm",
iconPosition: "left",
},
argTypes: {
size: {
options: ["sm", "md"],
control: { type: "radio" },
description: "To select the size of icon and font-size",
},
},
} satisfies Meta<typeof MockTab>;

export default meta;

const placeholder = {
sm: <StandalonePlaceholderRegularIcon iconSize="sm" />,
md: <StandalonePlaceholderRegularIcon iconSize="md" />,
};
export const TabWithIconsOnLeft = (args: ComponentProps<typeof MockTab>) => (
<MockTab
{...args}
iconPosition="left"
icon={placeholder[args?.size as keyof typeof placeholder]}
/>
);
export const TabWithIconsOnTop = (args: ComponentProps<typeof MockTab>) => (
<MockTab
{...args}
iconPosition="top"
icon={placeholder[args?.size as keyof typeof placeholder]}
/>
);
15 changes: 15 additions & 0 deletions lib/components/tabs/trigger/__snapshots__/trigger.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<Tab.Trigger/> should render correctly with size md 1`] = `
<p>
md
trigger
</p>
`;

exports[`<Tab.Trigger/> should render correctly with size sm 1`] = `
<p>
sm
trigger
</p>
`;
Loading

0 comments on commit 0412595

Please sign in to comment.