Skip to content

Commit

Permalink
refactor: use our base Link component in the sidebar (#327)
Browse files Browse the repository at this point in the history
  • Loading branch information
crnkovic authored Nov 1, 2023
1 parent 6b20a3b commit 92f249a
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 100 deletions.
151 changes: 80 additions & 71 deletions resources/js/Components/Link.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Link as InertiaLink } from "@inertiajs/react";
import cn from "classnames";
import { type MouseEvent, useMemo } from "react";
import { forwardRef, type MouseEvent, useMemo } from "react";
import { Icon } from "@/Components/Icon";
import { useExternalLinkContext } from "@/Contexts/ExternalLinkContext";

Expand All @@ -16,7 +16,7 @@ interface ClassNameProperties {
interface Properties extends ClassNameProperties {
external?: boolean;
href: string;
children: React.ReactNode;
children?: React.ReactNode;
"data-testid"?: string;
showExternalIcon?: boolean;
confirmBeforeProceeding?: boolean;
Expand Down Expand Up @@ -84,82 +84,91 @@ export const LinkButton = ({
/>
);

export const Link = ({
external = false,
disabled = false,
useAnchorTag = false,
variant,
className,
fontSize,
textColor,
showExternalIcon = true,
confirmBeforeProceeding = false,
iconClassName,
children,
href,
...properties
}: Properties): JSX.Element => {
const { openConfirmationModal, hasDisabledLinkWarning, isDomainAllowed } = useExternalLinkContext();

if (external) {
const handleExternalClick = (event: MouseEvent<HTMLElement>): void => {
if (confirmBeforeProceeding) {
if (!hasDisabledLinkWarning && !isDomainAllowed(href)) {
event.preventDefault();
event.stopPropagation();

openConfirmationModal(href);
export const Link = forwardRef<HTMLAnchorElement, Properties>(
(
{
external = false,
disabled = false,
useAnchorTag = false,
variant,
className,
fontSize,
textColor,
showExternalIcon = true,
confirmBeforeProceeding = false,
iconClassName,
children,
href,
...properties
}: Properties,
reference,
): JSX.Element => {
const { openConfirmationModal, hasDisabledLinkWarning, isDomainAllowed } = useExternalLinkContext();

if (external) {
const handleExternalClick = (event: MouseEvent<HTMLElement>): void => {
if (confirmBeforeProceeding) {
if (!hasDisabledLinkWarning && !isDomainAllowed(href)) {
event.preventDefault();
event.stopPropagation();

openConfirmationModal(href);
}
return;
}
return;
}

stopPropagationAndBlur(event);
};

return (
<a
data-testid="Link__anchor"
href={href}
target="_blank"
rel="noreferrer"
className={variantClassName({ variant, className, fontSize, textColor, disabled })}
onClick={handleExternalClick}
{...properties}
>
{children}
stopPropagationAndBlur(event);
};

return (
<a
data-testid="Link__anchor"
href={href}
target="_blank"
ref={reference}
rel="noreferrer"
className={variantClassName({ variant, className, fontSize, textColor, disabled })}
onClick={handleExternalClick}
{...properties}
>
{children}

{showExternalIcon && (
<Icon
name="ArrowExternalSmall"
size="sm"
className={iconClassName ?? "ml-1 text-theme-secondary-500"}
/>
)}
</a>
);
}

{showExternalIcon && (
<Icon
name="ArrowExternalSmall"
size="sm"
className={iconClassName ?? "ml-1 text-theme-secondary-500"}
/>
)}
</a>
);
}
if (useAnchorTag) {
return (
<a
href={href}
ref={reference}
className={variantClassName({ variant, className, fontSize, textColor, disabled })}
{...properties}
data-testid="Link__anchor"
>
{children}
</a>
);
}

if (useAnchorTag) {
return (
<a
<InertiaLink
{...properties}
href={href}
disabled={disabled}
className={variantClassName({ variant, className, fontSize, textColor, disabled })}
{...properties}
data-testid="Link__anchor"
>
{children}
</a>
</InertiaLink>
);
}

return (
<InertiaLink
{...properties}
href={href}
disabled={disabled}
className={variantClassName({ variant, className, fontSize, textColor, disabled })}
>
{children}
</InertiaLink>
);
};
},
);

Link.displayName = "Link";
11 changes: 9 additions & 2 deletions resources/js/Components/Sidebar/Sidebar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ describe("Sidebar", () => {
subtitle="Customize your App Experience"
>
<SidebarItem
href="#"
icon="Cog"
title="General"
/>

<SidebarItem
href="#"
icon="Bell"
title="Notifications"
/>
Expand All @@ -29,30 +31,35 @@ describe("Sidebar", () => {

expect(screen.getByText("Settings")).toBeTruthy();
expect(screen.getByText("Customize your App Experience")).toBeTruthy();
expect(screen.getAllByTestId("SidebarItem")).toHaveLength(3);
expect(screen.getAllByTestId("SidebarItem")).toHaveLength(2);
expect(screen.getAllByTestId("SidebarItem__disabled")).toHaveLength(1);
});

it("should not render sidebar head if title and subtitle are undefined", () => {
render(
<Sidebar>
<SidebarItem
href="/first"
icon="Cog"
title="General"
/>

<SidebarItem
isDisabled
icon="Bell"
title="Notifications"
/>

<SidebarItem
isDisabled
icon="Laptop"
title="Sessions History"
/>
</Sidebar>,
);

expect(screen.queryByText("SidebarHead")).not.toBeTruthy();
expect(screen.getAllByTestId("SidebarItem")).toHaveLength(3);
expect(screen.getAllByTestId("SidebarItem")).toHaveLength(1);
expect(screen.getAllByTestId("SidebarItem__disabled")).toHaveLength(2);
});
});
61 changes: 42 additions & 19 deletions resources/js/Components/Sidebar/SidebarItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,47 @@ export const SidebarItem = ({
content={tooltip}
disabled={tooltip === undefined}
>
<Tabs.Link
tabIndex={0}
title={title}
selected={isSelected}
disabled={isDisabled}
href={href}
data-testid="SidebarItem"
>
{isTruthy(icon) && (
<Icon
className={cn("transition-default ml-0 mr-2", {
"border-transparent text-theme-primary-600 dark:text-theme-dark-50": isSelected,
})}
name={icon}
size="lg"
/>
)}
<div>{title}</div>
</Tabs.Link>
{isDisabled || href === undefined ? (
<Tabs.DisabledLink
tabIndex={0}
title={title}
selected={isSelected}
disabled={isDisabled}
data-testid="SidebarItem__disabled"
>
{isTruthy(icon) && (
<Icon
className={cn("transition-default ml-0 mr-2", {
"border-transparent text-theme-primary-600 dark:text-theme-dark-50": isSelected,
})}
name={icon}
size="lg"
/>
)}

<span>{title}</span>
</Tabs.DisabledLink>
) : (
<Tabs.Link
tabIndex={0}
title={title}
selected={isSelected}
disabled={isDisabled}
href={href}
data-testid="SidebarItem"
>
{isTruthy(icon) && (
<Icon
className={cn("transition-default ml-0 mr-2", {
"border-transparent text-theme-primary-600 dark:text-theme-dark-50": isSelected,
})}
name={icon}
size="lg"
/>
)}

<span>{title}</span>
</Tabs.Link>
)}
</Tooltip>
);
23 changes: 18 additions & 5 deletions resources/js/Components/Tabs.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe("Tabs", () => {

it("should render tab link", () => {
render(
<Tabs.Link href="https://ardenthq.com">
<Tabs.Link href="https://ardenthq.com/">
<span data-testid="test">Click Me</span>
</Tabs.Link>,
);
Expand All @@ -48,7 +48,17 @@ describe("Tabs", () => {

expect(screen.getByTestId("test").parentElement?.tagName).toBe("A");

expect(screen.getByTestId("test").parentElement?.getAttribute("href")).toBe("https://ardenthq.com");
expect(screen.getByTestId("test").parentElement?.getAttribute("href")).toBe("https://ardenthq.com/");
});

it("should render disabled link", () => {
render(
<Tabs.DisabledLink>
<span data-testid="test">Click Me</span>
</Tabs.DisabledLink>,
);

expect(screen.getByTestId("test").parentElement?.tagName).toBe("SPAN");
});

it("has click event", async () => {
Expand Down Expand Up @@ -87,7 +97,10 @@ describe("Tabs", () => {

it("marks link as selected", () => {
render(
<Tabs.Link selected>
<Tabs.Link
href="#"
selected
>
<span data-testid="test">Click Me</span>
</Tabs.Link>,
);
Expand All @@ -96,9 +109,9 @@ describe("Tabs", () => {
});
it("marks link as disabled", () => {
render(
<Tabs.Link disabled>
<Tabs.DisabledLink disabled>
<span data-testid="test">Click Me</span>
</Tabs.Link>,
</Tabs.DisabledLink>,
);

expect(screen.getByTestId("test").parentElement?.className).toContain("cursor-not-allowed");
Expand Down
19 changes: 16 additions & 3 deletions resources/js/Components/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import cn from "classnames";
import { type AnchorHTMLAttributes, type ButtonHTMLAttributes, forwardRef, type HTMLAttributes } from "react";
import { Icon, type IconName } from "./Icon";
import { Link as InertiaLink } from "@/Components/Link";

interface TabAnchorProperties extends AnchorHTMLAttributes<HTMLAnchorElement> {
interface TabAnchorProperties extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href"> {
href: string;
disabled?: boolean;
selected?: boolean;
}
Expand Down Expand Up @@ -75,7 +77,7 @@ const getTabClasses = ({

const Link = forwardRef<HTMLAnchorElement, TabAnchorProperties>(
({ className, disabled = false, selected = false, ...properties }, reference): JSX.Element => (
<a
<InertiaLink
ref={reference}
{...properties}
className={getTabClasses({ variant: "vertical", disabled, selected, className })}
Expand All @@ -84,6 +86,17 @@ const Link = forwardRef<HTMLAnchorElement, TabAnchorProperties>(
);
Link.displayName = "Tabs.Link";

const DisabledLink = forwardRef<HTMLAnchorElement, Omit<TabAnchorProperties, "href">>(
({ className, disabled = false, selected = false, ...properties }, reference): JSX.Element => (
<span
ref={reference}
{...properties}
className={getTabClasses({ variant: "vertical", disabled, selected, className })}
/>
),
);
DisabledLink.displayName = "Tabs.DisabledLink";

const Button = forwardRef<HTMLButtonElement, TabButtonProperties>(
({ className, disabled, selected = false, children, icon, ...properties }, reference): JSX.Element => (
<button
Expand Down Expand Up @@ -121,4 +134,4 @@ export const List = ({ className, ...properties }: WrapperProperties): JSX.Eleme
</div>
);

export const Tabs = Object.assign(List, { Button, Link });
export const Tabs = Object.assign(List, { Button, Link, DisabledLink });

0 comments on commit 92f249a

Please sign in to comment.