Skip to content

Commit

Permalink
Use the Link component for tabs (#653)
Browse files Browse the repository at this point in the history
* Use the `Link` component for navigating tabs

* Use `useNavigation` instead

* Refactor stories
  • Loading branch information
josepjaume authored Oct 7, 2024
1 parent 482a544 commit 69bf6b6
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 51 deletions.
4 changes: 2 additions & 2 deletions lib/components/Actions/Link/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import ExternalLink from "@/icons/ExternalLink"
import {
Link as BaseLink,
LinkProps as BaseLinkProps,
useLink,
useNavigation,
} from "@/lib/linkHandler"
import { cn } from "@/lib/utils"
import { cva, VariantProps } from "class-variance-authority"
Expand Down Expand Up @@ -35,7 +35,7 @@ export const Link = forwardRef<HTMLAnchorElement, LinkProps>(function Link(
) {
const { target } = props
const external = target === "_blank"
const { isActive } = useLink()
const { isActive } = useNavigation()

return (
<BaseLink
Expand Down
57 changes: 24 additions & 33 deletions lib/experimental/Navigation/Tabs/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { action } from "@storybook/addon-actions"
import { useNavigation } from "@/lib/linkHandler"
import type { Meta, StoryObj } from "@storybook/react"
import { useState } from "react"
import { Tabs } from "."

const tabItems = [
{ label: "Overview", link: "/" },
{ label: "Courses", link: "/courses" },
{ label: "Categories", link: "/categories" },
{ label: "Catalog", link: "/catalog" },
{ label: "Requests", link: "/requests" },
]

const meta: Meta<typeof Tabs> = {
component: Tabs,
tags: ["autodocs"],
Expand All @@ -11,50 +18,34 @@ const meta: Meta<typeof Tabs> = {
control: "boolean",
},
},
render: ({ secondary = false }: { secondary?: boolean }) => {
const { isActive } = useNavigation()
const activeTab = tabItems.find((tab) =>
isActive(tab.link, { exact: true })
)

return (
<div onClick={(e) => e.preventDefault()}>
<Tabs tabs={tabItems} secondary={secondary} />
<p className="mt-4 flex h-full min-h-60 items-center justify-center rounded-lg bg-f1-background-secondary/50 p-4 text-f1-foreground-secondary">
{activeTab?.label}
</p>
</div>
)
},
}

export default meta
type Story = StoryObj<typeof Tabs>

const tabItems = [
{ label: "Overview", link: "/overview" },
{ label: "Courses", link: "/courses" },
{ label: "Categories", link: "/categories" },
{ label: "Catalog", link: "/catalog" },
{ label: "Requests", link: "/requests" },
]

const TabsExample = ({ secondary = false }: { secondary?: boolean }) => {
const [activeTab, setActiveTab] = useState("Overview")

return (
<div onClick={(e) => e.preventDefault()}>
<Tabs
tabs={tabItems}
activeTab={activeTab}
onTabChange={(tab) => {
setActiveTab(tab.label)
action("Tab changed")(tab)
}}
secondary={secondary}
/>
<p className="mt-4 flex h-full min-h-60 items-center justify-center rounded-lg bg-f1-background-secondary/50 p-4 text-f1-foreground-secondary">
{activeTab}
</p>
</div>
)
}

export const Primary: Story = {
args: {
secondary: false,
},
render: (args) => <TabsExample {...args} />,
}

export const Secondary: Story = {
args: {
secondary: true,
},
render: (args) => <TabsExample {...args} />,
}
18 changes: 7 additions & 11 deletions lib/experimental/Navigation/Tabs/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Link, useNavigation } from "@/lib/linkHandler"
import { TabNavigation, TabNavigationLink } from "@/ui/tab-navigation"

interface TabItem {
Expand All @@ -7,28 +8,23 @@ interface TabItem {

interface TabsProps {
tabs: TabItem[]
activeTab: string
onTabChange: (tab: TabItem) => void
secondary?: boolean
}

export function Tabs({
tabs,
activeTab,
onTabChange,
secondary = false,
}: TabsProps) {
export function Tabs({ tabs, secondary = false }: TabsProps) {
const { isActive } = useNavigation()

return (
<TabNavigation secondary={secondary}>
{tabs.map((tab) => (
<TabNavigationLink
key={tab.label}
active={activeTab === tab.label}
onClick={() => onTabChange(tab)}
active={isActive(tab.link, { exact: true })}
href={tab.link}
secondary={secondary}
asChild
>
{tab.label}
<Link href={tab.link}>{tab.label}</Link>
</TabNavigationLink>
))}
</TabNavigation>
Expand Down
4 changes: 2 additions & 2 deletions lib/lib/linkHandler.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Link, LinkProvider, useLink } from "@/lib/linkHandler"
import { Link, LinkProvider, useNavigation } from "@/lib/linkHandler"
import { render, screen } from "@testing-library/react"
import { describe, expect, test } from "vitest"

Expand All @@ -17,7 +17,7 @@ describe("LinkProvider", () => {

describe("useLink", () => {
const Component: React.FC<{ href: string }> = ({ href }) => {
const { isActive } = useLink()
const { isActive } = useNavigation()
return <a href={href} data-is-active={isActive(href)} />
}

Expand Down
11 changes: 8 additions & 3 deletions lib/lib/linkHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,28 @@ export const useLinkContext = () => {

export type LinkProps = AnchorHTMLAttributes<HTMLAnchorElement>

export const useLink = () => {
export const useNavigation = () => {
const { currentPath } = useLinkContext()

const isActive = (path: string | undefined) => {
const isActive = (
path: string | undefined,
{ exact }: { exact: boolean } = { exact: false }
) => {
if (currentPath === undefined || path === undefined) return false
if (exact) return currentPath === path
return currentPath.startsWith(path)
}

return {
currentPath,
isActive,
}
}

export const Link = forwardRef<HTMLAnchorElement, LinkProps>(
function Link(props, ref) {
const { component } = useLinkContext()
const { isActive } = useLink()
const { isActive } = useNavigation()

const overridenProps = {
"data-is-active": isActive(props.href) || undefined,
Expand Down

0 comments on commit 69bf6b6

Please sign in to comment.