first commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
/* TODO: Implement NavigationItem */
|
||||
export {};
|
||||
7
calcom/packages/ui/components/navigation/index.tsx
Normal file
7
calcom/packages/ui/components/navigation/index.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
export { default as HorizontalTabItem } from "./tabs/HorizontalTabItem";
|
||||
export type { HorizontalTabItemProps } from "./tabs/HorizontalTabItem";
|
||||
export { default as HorizontalTabs } from "./tabs/HorizontalTabs";
|
||||
export type { NavTabProps } from "./tabs/HorizontalTabs";
|
||||
export { default as VerticalTabItem } from "./tabs/VerticalTabItem";
|
||||
export type { VerticalTabItemProps } from "./tabs/VerticalTabItem";
|
||||
export { default as VerticalTabs } from "./tabs/VerticalTabs";
|
||||
@@ -0,0 +1,71 @@
|
||||
import Link from "next/link";
|
||||
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { useUrlMatchesCurrentUrl } from "@calcom/lib/hooks/useUrlMatchesCurrentUrl";
|
||||
|
||||
import { Icon, type IconName } from "../../..";
|
||||
import { Avatar } from "../../avatar";
|
||||
import { SkeletonText } from "../../skeleton";
|
||||
|
||||
export type HorizontalTabItemProps = {
|
||||
name: string;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
target?: string;
|
||||
href: string;
|
||||
linkShallow?: boolean;
|
||||
linkScroll?: boolean;
|
||||
icon?: IconName;
|
||||
avatar?: string;
|
||||
};
|
||||
|
||||
const HorizontalTabItem = function ({
|
||||
name,
|
||||
href,
|
||||
linkShallow,
|
||||
linkScroll,
|
||||
avatar,
|
||||
...props
|
||||
}: HorizontalTabItemProps) {
|
||||
const { t, isLocaleReady } = useLocale();
|
||||
|
||||
const isCurrent = useUrlMatchesCurrentUrl(href);
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={name}
|
||||
href={href}
|
||||
shallow={linkShallow}
|
||||
scroll={linkScroll}
|
||||
className={classNames(
|
||||
isCurrent ? "bg-emphasis text-emphasis" : "hover:bg-subtle hover:text-emphasis text-default",
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-[6px] p-2 text-sm font-medium leading-4 transition md:mb-0",
|
||||
props.disabled && "pointer-events-none !opacity-30",
|
||||
props.className
|
||||
)}
|
||||
target={props.target ? props.target : undefined}
|
||||
data-testid={`horizontal-tab-${name}`}
|
||||
aria-current={isCurrent ? "page" : undefined}>
|
||||
{props.icon && (
|
||||
<Icon
|
||||
name={props.icon}
|
||||
className={classNames(
|
||||
isCurrent ? "text-emphasis" : "group-hover:text-subtle text-muted",
|
||||
"-ml-0.5 hidden h-4 w-4 ltr:mr-2 rtl:ml-2 sm:inline-block"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
{isLocaleReady ? (
|
||||
<div className="flex items-center gap-x-2">
|
||||
{avatar && <Avatar size="sm" imageSrc={avatar} alt="avatar" />} {t(name)}
|
||||
</div>
|
||||
) : (
|
||||
<SkeletonText className="h-4 w-24" />
|
||||
)}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default HorizontalTabItem;
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { HorizontalTabItemProps } from "./HorizontalTabItem";
|
||||
import HorizontalTabItem from "./HorizontalTabItem";
|
||||
|
||||
export interface NavTabProps {
|
||||
tabs: HorizontalTabItemProps[];
|
||||
linkShallow?: boolean;
|
||||
linkScroll?: boolean;
|
||||
actions?: JSX.Element;
|
||||
}
|
||||
|
||||
const HorizontalTabs = function ({ tabs, linkShallow, linkScroll, actions, ...props }: NavTabProps) {
|
||||
return (
|
||||
<div className="mb-4 h-9 max-w-full lg:mb-5">
|
||||
<nav
|
||||
className="no-scrollbar flex max-h-9 space-x-1 overflow-x-scroll rounded-md"
|
||||
aria-label="Tabs"
|
||||
{...props}>
|
||||
{tabs.map((tab, idx) => (
|
||||
<HorizontalTabItem
|
||||
className="px-4 py-2.5"
|
||||
{...tab}
|
||||
key={idx}
|
||||
linkShallow={linkShallow}
|
||||
linkScroll={linkScroll}
|
||||
/>
|
||||
))}
|
||||
</nav>
|
||||
{actions && actions}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HorizontalTabs;
|
||||
@@ -0,0 +1,111 @@
|
||||
import Link from "next/link";
|
||||
import { Fragment } from "react";
|
||||
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { useUrlMatchesCurrentUrl } from "@calcom/lib/hooks/useUrlMatchesCurrentUrl";
|
||||
|
||||
import { Icon, type IconName } from "../../..";
|
||||
import { Skeleton } from "../../skeleton";
|
||||
|
||||
export type VerticalTabItemProps = {
|
||||
name: string;
|
||||
info?: string;
|
||||
icon?: IconName;
|
||||
disabled?: boolean;
|
||||
children?: VerticalTabItemProps[];
|
||||
textClassNames?: string;
|
||||
className?: string;
|
||||
isChild?: boolean;
|
||||
hidden?: boolean;
|
||||
disableChevron?: boolean;
|
||||
href: string;
|
||||
isExternalLink?: boolean;
|
||||
linkShallow?: boolean;
|
||||
linkScroll?: boolean;
|
||||
avatar?: string;
|
||||
iconClassName?: string;
|
||||
};
|
||||
|
||||
const VerticalTabItem = ({
|
||||
name,
|
||||
href,
|
||||
info,
|
||||
isChild,
|
||||
disableChevron,
|
||||
linkShallow,
|
||||
linkScroll,
|
||||
...props
|
||||
}: VerticalTabItemProps) => {
|
||||
const { t } = useLocale();
|
||||
const isCurrent = useUrlMatchesCurrentUrl(href);
|
||||
|
||||
return (
|
||||
<Fragment key={name}>
|
||||
{!props.hidden && (
|
||||
<>
|
||||
<Link
|
||||
key={name}
|
||||
href={href}
|
||||
shallow={linkShallow}
|
||||
scroll={linkScroll}
|
||||
target={props.isExternalLink ? "_blank" : "_self"}
|
||||
className={classNames(
|
||||
props.textClassNames || "text-default text-sm font-medium leading-none",
|
||||
"min-h-7 hover:bg-subtle [&[aria-current='page']]:bg-emphasis [&[aria-current='page']]:text-emphasis group-hover:text-default group flex w-64 flex-row items-center rounded-md px-3 py-2 transition",
|
||||
props.disabled && "pointer-events-none !opacity-30",
|
||||
(isChild || !props.icon) && "ml-7 w-auto ltr:mr-5 rtl:ml-5",
|
||||
!info ? "h-6" : "h-auto",
|
||||
props.className
|
||||
)}
|
||||
data-testid={`vertical-tab-${name}`}
|
||||
aria-current={isCurrent ? "page" : undefined}>
|
||||
{props.icon && (
|
||||
<Icon
|
||||
name={props.icon}
|
||||
className={classNames(
|
||||
"mr-2 h-[16px] w-[16px] stroke-[2px] ltr:mr-2 rtl:ml-2 md:mt-0",
|
||||
props.iconClassName
|
||||
)}
|
||||
data-testid="icon-component"
|
||||
/>
|
||||
)}
|
||||
<div className="h-fit">
|
||||
<span className="flex items-center space-x-2 rtl:space-x-reverse">
|
||||
<Skeleton title={t(name)} as="p" className="max-w-36 min-h-4 truncate">
|
||||
{t(name)}
|
||||
</Skeleton>
|
||||
{props.isExternalLink ? <Icon name="external-link" data-testid="external-link" /> : null}
|
||||
</span>
|
||||
{info && (
|
||||
<Skeleton
|
||||
data-testid="apps-info"
|
||||
as="p"
|
||||
title={t(info)}
|
||||
className="max-w-44 mt-1 truncate text-xs font-normal">
|
||||
{t(info)}
|
||||
</Skeleton>
|
||||
)}
|
||||
</div>
|
||||
{!disableChevron && isCurrent && (
|
||||
<div className="ml-auto self-center">
|
||||
<Icon
|
||||
name="chevron-right"
|
||||
width={20}
|
||||
height={20}
|
||||
className="text-default h-auto w-[20px] stroke-[1.5px]"
|
||||
data-testid="chevron-right"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
{props.children?.map((child) => (
|
||||
<VerticalTabItem key={child.name} {...child} isChild />
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerticalTabItem;
|
||||
@@ -0,0 +1,54 @@
|
||||
import { classNames } from "@calcom/lib";
|
||||
|
||||
import type { VerticalTabItemProps } from "./VerticalTabItem";
|
||||
import VerticalTabItem from "./VerticalTabItem";
|
||||
|
||||
export { VerticalTabItem };
|
||||
|
||||
export interface NavTabProps {
|
||||
tabs: VerticalTabItemProps[];
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
sticky?: boolean;
|
||||
linkShallow?: boolean;
|
||||
linkScroll?: boolean;
|
||||
itemClassname?: string;
|
||||
iconClassName?: string;
|
||||
}
|
||||
|
||||
const NavTabs = function ({
|
||||
tabs,
|
||||
className = "",
|
||||
sticky,
|
||||
linkShallow,
|
||||
linkScroll,
|
||||
itemClassname,
|
||||
iconClassName,
|
||||
...props
|
||||
}: NavTabProps) {
|
||||
return (
|
||||
<nav
|
||||
className={classNames(
|
||||
`no-scrollbar flex flex-col space-y-0.5 overflow-scroll ${className}`,
|
||||
sticky && "sticky top-0 -mt-7"
|
||||
)}
|
||||
aria-label="Tabs"
|
||||
{...props}>
|
||||
{/* padding top for sticky */}
|
||||
{sticky && <div className="pt-6" />}
|
||||
{props.children}
|
||||
{tabs.map((tab, idx) => (
|
||||
<VerticalTabItem
|
||||
{...tab}
|
||||
key={idx}
|
||||
linkShallow={linkShallow}
|
||||
linkScroll={linkScroll}
|
||||
className={itemClassname}
|
||||
iconClassName={iconClassName}
|
||||
/>
|
||||
))}
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
export default NavTabs;
|
||||
@@ -0,0 +1,134 @@
|
||||
import { Canvas, Meta, Story } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import {
|
||||
Title,
|
||||
VariantRow,
|
||||
VariantsTable,
|
||||
CustomArgsTable,
|
||||
Examples,
|
||||
Example,
|
||||
} from "@calcom/storybook/components";
|
||||
import { Icon } from "@calcom/ui";
|
||||
|
||||
import HorizontalTabs from "../HorizontalTabs";
|
||||
|
||||
<Meta title="UI/Navigation/HorizontalTabs" component={HorizontalTabs} />
|
||||
|
||||
<Title title="Horizontal Tabs" suffix="Brief" subtitle="Version 1.0 — Last Update: 17 Aug 2023" />
|
||||
|
||||
## Definition
|
||||
|
||||
The HorizontalTabs component is a user interface element used for displaying a horizontal set of tabs, often employed for navigation or organization purposes within a web application.
|
||||
|
||||
## Structure
|
||||
|
||||
The HorizontalTabs component is designed to work alongside the HorizontalTabItem component, which represents individual tabs within the tab bar.
|
||||
|
||||
export const tabs = [
|
||||
{
|
||||
name: "Tab 1",
|
||||
href: "?path=/story/ui-navigation-horizontaltabs--horizontal-tabs/tab1",
|
||||
disabled: false,
|
||||
linkShallow: true,
|
||||
linkScroll: true,
|
||||
icon: Plus,
|
||||
},
|
||||
{
|
||||
name: "Tab 2",
|
||||
href: "?path=/story/ui-navigation-horizontaltabs--horizontal-tabs/tab2",
|
||||
disabled: false,
|
||||
linkShallow: true,
|
||||
linkScroll: true,
|
||||
avatar: "Avatar",
|
||||
},
|
||||
{
|
||||
name: "Tab 3",
|
||||
href: "?path=/story/ui-navigation-horizontaltabs--horizontal-tabs/tab3",
|
||||
disabled: true,
|
||||
linkShallow: true,
|
||||
linkScroll: true,
|
||||
},
|
||||
];
|
||||
|
||||
<CustomArgsTable of={HorizontalTabs} />
|
||||
|
||||
<Examples title="Default">
|
||||
<Example title="Default">
|
||||
<HorizontalTabs
|
||||
tabs={[
|
||||
{
|
||||
name: "tab 1",
|
||||
href: "?path=/story/ui-navigation-horizontaltabs--horizontal-tabs/tab1",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Example>
|
||||
<Example title="With avatar">
|
||||
<HorizontalTabs
|
||||
tabs={[
|
||||
{
|
||||
name: "Tab 1",
|
||||
href: "?path=/story/ui-navigation-horizontaltabs--horizontal-tabs/tab1",
|
||||
avatar: "Avatar",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Example>
|
||||
<Example title="With icon">
|
||||
<HorizontalTabs
|
||||
tabs={[
|
||||
{
|
||||
name: "Tab 1",
|
||||
href: "?path=/story/ui-navigation-horizontaltabs--horizontal-tabs/tab1",
|
||||
icon: Plus,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Example>
|
||||
<Example title="Disabled">
|
||||
<HorizontalTabs
|
||||
tabs={[
|
||||
{
|
||||
name: "Tab 1",
|
||||
href: "?path=/story/ui-navigation-horizontaltabs--horizontal-tabs/tab1",
|
||||
disabled: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Example>
|
||||
</Examples>
|
||||
|
||||
## HorizontalTabs Story
|
||||
|
||||
<Canvas>
|
||||
<Story
|
||||
name="Horizontal Tabs"
|
||||
args={{
|
||||
name: "Tab 1",
|
||||
href: "/tab1",
|
||||
disabled: false,
|
||||
className: "",
|
||||
linkShallow: true,
|
||||
linkScroll: true,
|
||||
icon: "",
|
||||
avatar: "",
|
||||
}}
|
||||
argTypes={{
|
||||
name: { control: "text", description: "Tab name" },
|
||||
href: { control: "text", description: "Tab link" },
|
||||
disabled: { control: "boolean", description: "Whether the tab is disabled" },
|
||||
className: { control: "text", description: "Additional CSS class" },
|
||||
linkShallow: { control: "boolean", description: "Whether to use shallow links" },
|
||||
linkScroll: { control: "boolean", description: "Whether to scroll to links" },
|
||||
icon: { control: "text", description: "SVGComponent icon" },
|
||||
avatar: { control: "text", description: "Avatar image URL" },
|
||||
}}>
|
||||
{(...props) => (
|
||||
<VariantsTable titles={["Default"]} columnMinWidth={150}>
|
||||
<VariantRow>
|
||||
<HorizontalTabs tabs={tabs} className="overflow-hidden" actions={<button>Click me</button>} />
|
||||
</VariantRow>
|
||||
</VariantsTable>
|
||||
)}
|
||||
</Story>
|
||||
</Canvas>
|
||||
@@ -0,0 +1,158 @@
|
||||
import { Meta, Story, Canvas } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import {
|
||||
Title,
|
||||
CustomArgsTable,
|
||||
Examples,
|
||||
Example,
|
||||
VariantsTable,
|
||||
VariantRow,
|
||||
} from "@calcom/storybook/components";
|
||||
import { Icon } from "@calcom/ui";
|
||||
|
||||
import VerticalTabs from "../VerticalTabs";
|
||||
|
||||
<Meta title="UI/Navigation/VerticalTabs" component={VerticalTabs} />
|
||||
|
||||
<Title title="Vertical Tabs Brief" subtitle="Version 1.0 — Last Update: 17 Aug 2023" />
|
||||
|
||||
## Definition
|
||||
|
||||
The VerticalTabs component is a user interface element utilized to present a vertical set of tabs, commonly employed for navigation or organizing content within a web application.
|
||||
|
||||
## Structure
|
||||
|
||||
The VerticalTabs component is designed to complement the HorizontalTabItem component, which represents individual tabs within the tab bar. This combination allows for creating intuitive navigation experiences and organized content presentation.
|
||||
|
||||
export const tabs = [
|
||||
{
|
||||
name: "Tab 1",
|
||||
href: "/tab1",
|
||||
disabled: false,
|
||||
linkShallow: true,
|
||||
linkScroll: true,
|
||||
disableChevron: true,
|
||||
icon: Plus,
|
||||
},
|
||||
{
|
||||
name: "Tab 2",
|
||||
href: "/tab2",
|
||||
disabled: false,
|
||||
linkShallow: true,
|
||||
linkScroll: true,
|
||||
avatar: "Avatar",
|
||||
},
|
||||
{
|
||||
name: "Tab 3",
|
||||
href: "/tab3",
|
||||
disabled: true,
|
||||
linkShallow: true,
|
||||
linkScroll: true,
|
||||
},
|
||||
];
|
||||
|
||||
<CustomArgsTable of={VerticalTabs} />
|
||||
|
||||
<Examples title="Default">
|
||||
<Example title="Default">
|
||||
<VerticalTabs
|
||||
tabs={[
|
||||
{
|
||||
name: "tab 1",
|
||||
href: "/tab1",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Example>
|
||||
<Example title="Disabled chevron">
|
||||
<VerticalTabs
|
||||
tabs={[
|
||||
{
|
||||
name: "Tab 1",
|
||||
href: "/tab1",
|
||||
disableChevron: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Example>
|
||||
<Example title="With icon">
|
||||
<VerticalTabs
|
||||
tabs={[
|
||||
{
|
||||
name: "Tab 1",
|
||||
href: "/tab1",
|
||||
icon: Plus,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Example>
|
||||
<Example title="Disabled">
|
||||
<VerticalTabs
|
||||
tabs={[
|
||||
{
|
||||
name: "Tab 1",
|
||||
href: "/tab1",
|
||||
disabled: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Example>
|
||||
</Examples>
|
||||
|
||||
## VerticalTabs Story
|
||||
|
||||
<Canvas>
|
||||
<Story
|
||||
name="Vertical Tabs"
|
||||
args={{
|
||||
name: "Tab 1",
|
||||
info: "Tab information",
|
||||
icon: Plus,
|
||||
disabled: false,
|
||||
children: [
|
||||
{
|
||||
name: "Sub Tab 1",
|
||||
href: "/sub-tab1",
|
||||
disabled: false,
|
||||
className: "sub-tab",
|
||||
},
|
||||
],
|
||||
textClassNames: "",
|
||||
className: "",
|
||||
isChild: false,
|
||||
hidden: false,
|
||||
disableChevron: true,
|
||||
href: "/tab1",
|
||||
isExternalLink: true,
|
||||
linkShallow: true,
|
||||
linkScroll: true,
|
||||
avatar: "",
|
||||
iconClassName: "",
|
||||
}}
|
||||
argTypes={{
|
||||
name: { control: "text", description: "Tab name" },
|
||||
info: { control: "text", description: "Tab information" },
|
||||
icon: { control: "object", description: "SVGComponent icon" },
|
||||
disabled: { control: "boolean", description: "Whether the tab is disabled" },
|
||||
children: { control: "object", description: "Array of child tabs" },
|
||||
textClassNames: { control: "text", description: "Additional text class names" },
|
||||
className: { control: "text", description: "Additional CSS class" },
|
||||
isChild: { control: "boolean", description: "Whether the tab is a child tab" },
|
||||
hidden: { control: "boolean", description: "Whether the tab is hidden" },
|
||||
disableChevron: { control: "boolean", description: "Whether to disable the chevron" },
|
||||
href: { control: "text", description: "Tab link" },
|
||||
isExternalLink: { control: "boolean", description: "Whether the link is external" },
|
||||
linkShallow: { control: "boolean", description: "Whether to use shallow links" },
|
||||
linkScroll: { control: "boolean", description: "Whether to scroll to links" },
|
||||
avatar: { control: "text", description: "Avatar image URL" },
|
||||
iconClassName: { control: "text", description: "Additional icon class name" },
|
||||
}}>
|
||||
{(...props) => (
|
||||
<VariantsTable titles={["Default"]} columnMinWidth={150}>
|
||||
<VariantRow>
|
||||
<VerticalTabs tabs={tabs} className="overflow-hidden" />
|
||||
</VariantRow>
|
||||
</VariantsTable>
|
||||
)}
|
||||
</Story>
|
||||
</Canvas>
|
||||
@@ -0,0 +1,145 @@
|
||||
/* eslint-disable playwright/missing-playwright-await */
|
||||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import HorizontalTabs from "./HorizontalTabs";
|
||||
import VerticalTabs from "./VerticalTabs";
|
||||
|
||||
vi.mock("@calcom/lib/hooks/useUrlMatchesCurrentUrl", () => ({
|
||||
useUrlMatchesCurrentUrl() {
|
||||
return {
|
||||
route: "/",
|
||||
pathname: "",
|
||||
query: "",
|
||||
asPath: "",
|
||||
push: vi.fn(),
|
||||
events: {
|
||||
on: vi.fn(),
|
||||
off: vi.fn(),
|
||||
},
|
||||
beforePopState: vi.fn(() => null),
|
||||
prefetch: vi.fn(() => null),
|
||||
};
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/lib/hooks/useLocale", () => ({
|
||||
useLocale: () => {
|
||||
return {
|
||||
t: (str: string) => str,
|
||||
isLocaleReady: true,
|
||||
i18n: {
|
||||
language: "en",
|
||||
defaultLocale: "en",
|
||||
locales: ["en"],
|
||||
exists: () => false,
|
||||
},
|
||||
};
|
||||
},
|
||||
}));
|
||||
|
||||
describe("Tests for navigation folder", () => {
|
||||
describe("Test HorizontalTabs Component", () => {
|
||||
const mockTabs = [
|
||||
{ name: "Tab 1", href: "/tab1" },
|
||||
{ name: "Tab 2", href: "/tab2", avatar: "Avatar" },
|
||||
{ name: "Tab 3", href: "/tab3" },
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test("Should render tabs with correct name and href", () => {
|
||||
render(<HorizontalTabs tabs={mockTabs} />);
|
||||
mockTabs.forEach((tab) => {
|
||||
const tabLabelElement = screen.getByTestId(`horizontal-tab-${tab.name}`);
|
||||
expect(tabLabelElement).toBeInTheDocument();
|
||||
|
||||
const name = screen.getByText(tab.name);
|
||||
expect(name).toBeInTheDocument();
|
||||
expect(tabLabelElement).toHaveAttribute("href", tab.href);
|
||||
});
|
||||
});
|
||||
|
||||
test("Should render actions correctly", () => {
|
||||
const handleClick = vi.fn();
|
||||
const mockActions = <button onClick={handleClick}>Actions</button>;
|
||||
render(<HorizontalTabs tabs={mockTabs} actions={mockActions} />);
|
||||
const actionsElement = screen.getByText("Actions");
|
||||
expect(actionsElement).toBeInTheDocument();
|
||||
fireEvent.click(actionsElement);
|
||||
|
||||
expect(handleClick).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Test VerticalTabs Component", () => {
|
||||
const mockTabs = [
|
||||
{
|
||||
name: "Tab 1",
|
||||
href: "/tab1",
|
||||
disableChevron: true,
|
||||
disabled: true,
|
||||
icon: "plus" as const,
|
||||
},
|
||||
{ name: "Tab 2", href: "/tab2", isExternalLink: true },
|
||||
{ name: "Tab 3", href: "/tab3", info: "info" },
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test("Should render tabs with correct name and href", () => {
|
||||
render(<VerticalTabs tabs={mockTabs} />);
|
||||
mockTabs.forEach((tab) => {
|
||||
const tabLabelElement = screen.getByTestId(`vertical-tab-${tab.name}`);
|
||||
expect(tabLabelElement).toBeInTheDocument();
|
||||
|
||||
const name = screen.getByText(tab.name);
|
||||
expect(name).toBeInTheDocument();
|
||||
expect(tabLabelElement).toHaveAttribute("href", tab.href);
|
||||
});
|
||||
});
|
||||
|
||||
test("Should render correctly if props are passed", async () => {
|
||||
render(<VerticalTabs tabs={mockTabs} />);
|
||||
|
||||
const iconElement = await screen.findAllByTestId("icon-component");
|
||||
const externalLink = await screen.findAllByTestId("external-link");
|
||||
const chevronRight = await screen.findAllByTestId("chevron-right");
|
||||
|
||||
mockTabs.forEach((tab) => {
|
||||
const tabName = screen.getByText(tab.name);
|
||||
expect(tabName).toBeInTheDocument();
|
||||
|
||||
const aTag = screen.getByTestId(`vertical-tab-${tab.name}`);
|
||||
const tabContainer = tabName.closest("a");
|
||||
const infoElement = tabContainer?.querySelector("p[title='info']");
|
||||
|
||||
expect(chevronRight.length).toEqual(mockTabs.length - 1);
|
||||
if (tab.disabled) {
|
||||
expect(aTag).tabToBeDisabled();
|
||||
} else {
|
||||
expect(aTag).not.tabToBeDisabled();
|
||||
}
|
||||
|
||||
if (tab.info) {
|
||||
expect(infoElement).toBeInTheDocument();
|
||||
} else {
|
||||
expect(infoElement).toBeNull();
|
||||
}
|
||||
|
||||
if (tab.isExternalLink) {
|
||||
expect(aTag).toHaveAttribute("target", "_blank");
|
||||
} else {
|
||||
expect(aTag).toHaveAttribute("target", "_self");
|
||||
}
|
||||
});
|
||||
|
||||
expect(externalLink.length).toEqual(1);
|
||||
expect(iconElement.length).toEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user