Skip to content

Commit

Permalink
Add Breadcrumbs & Header (#666)
Browse files Browse the repository at this point in the history
* First take

* module name

* header

* Use item navigation

* change checks

* Exports

* breadcrumbs prop
  • Loading branch information
dani-moreno authored Oct 9, 2024
1 parent 993c686 commit 4feb3d2
Show file tree
Hide file tree
Showing 7 changed files with 247 additions and 8 deletions.
21 changes: 21 additions & 0 deletions lib/experimental/Navigation/Header/Breadcrumbs/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Meta, StoryObj } from "@storybook/react"
import Breadcrumbs from "./index"

const meta: Meta<typeof Breadcrumbs> = {
component: Breadcrumbs,
tags: ["autodocs"],
}

export default meta

type Story = StoryObj<typeof Breadcrumbs>

export const Default: Story = {
args: {
breadcrumbs: [
{ label: "Recruitment", href: "/recruitment", icon: "Recruitment" },
{ label: "Candidates", href: "/recruitment/candidates" },
{ label: "Dani Moreno" },
],
},
}
75 changes: 75 additions & 0 deletions lib/experimental/Navigation/Header/Breadcrumbs/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import {
Breadcrumb,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
BreadcrumbItem as ShadBreadcrumbItem,
} from "@/ui/breadcrumb"

import {
ModuleAvatar,
type IconType,
} from "@/experimental/Information/ModuleAvatar"

import { ChevronRight } from "@/icons"
import { Link } from "@/lib/linkHandler"
import { cn, focusRing } from "@/lib/utils"
import { NavigationItem } from "../../utils"

export type BreadcrumbItemType = NavigationItem & {
icon?: IconType
}

interface BreadcrumbItemProps {
item: BreadcrumbItemType
isLast: boolean
}

function BreadcrumbItem({ item, isLast }: BreadcrumbItemProps) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { label, ...props } = item

return (
<ShadBreadcrumbItem>
{!isLast ? (
<>
<BreadcrumbLink className={item.icon && "pl-0.5"} asChild>
<Link
{...props}
className={cn("flex items-center gap-1.5", focusRing())}
>
{item.icon && <ModuleAvatar icon={item.icon} size="sm" />}
{item.label}
</Link>
</BreadcrumbLink>
<BreadcrumbSeparator>
<ChevronRight className="h-4 w-4 text-f1-icon-secondary" />
</BreadcrumbSeparator>
</>
) : (
<BreadcrumbPage>{item.label}</BreadcrumbPage>
)}
</ShadBreadcrumbItem>
)
}

interface BreadcrumbsProps {
breadcrumbs: BreadcrumbItemType[]
}

export default function Breadcrumbs({ breadcrumbs }: BreadcrumbsProps) {
return (
<Breadcrumb>
<BreadcrumbList>
{breadcrumbs.map((item, index) => (
<BreadcrumbItem
key={index}
item={item}
isLast={index === breadcrumbs.length - 1}
/>
))}
</BreadcrumbList>
</Breadcrumb>
)
}
57 changes: 57 additions & 0 deletions lib/experimental/Navigation/Header/Header/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import EllipsisHorizontal from "@/icons/EllipsisHorizontal"
import Settings from "@/icons/Settings"

import type { Meta, StoryObj } from "@storybook/react"
import Header from "."

const meta: Meta<typeof Header> = {
component: Header,
tags: ["autodocs"],
}

export default meta
type Story = StoryObj<typeof Header>

export const Default: Story = {
args: {
module: {
name: "Recruitment",
href: "/recruitment",
icon: "Recruitment",
},
breadcrumbs: [
{ label: "Candidates", href: "/recruitment/candidates" },
{ label: "Dani Moreno" },
],
actions: [
{
label: "Settings",
icon: Settings,
onClick: () => console.log("Settings clicked"),
},
{
label: "More options",
icon: EllipsisHorizontal,
onClick: () => console.log("More clicked"),
},
],
menu: {
show: true,
onClick: () => console.log("Menu clicked"),
},
},
}

export const FirstLevel: Story = {
args: {
module: {
name: "Recruitment",
href: "/recruitment",
icon: "Recruitment",
},
menu: {
show: true,
onClick: () => console.log("Menu clicked"),
},
},
}
86 changes: 86 additions & 0 deletions lib/experimental/Navigation/Header/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { Button } from "@/components/Actions/Button"
import { IconType } from "@/components/Utilities/Icon"
import AlignTextJustify from "@/icons/AlignTextJustify"
import { cn } from "@/lib/utils"
import Breadcrumbs, { type BreadcrumbItemType } from "../Breadcrumbs"

import {
ModuleAvatar,
type IconType as ModuleAvatarIconType,
} from "@/experimental/Information/ModuleAvatar"

type HeaderProps = {
module: {
name: string
href: string
icon: ModuleAvatarIconType
}
breadcrumbs?: BreadcrumbItemType[]
actions?: {
label: string
icon: IconType
onClick: () => void
}[]
menu: {
show: boolean
onClick: () => void
}
}

export default function Header({
module,
breadcrumbs = [],
actions = [],
menu,
}: HeaderProps) {
const breadcrumbsTree: BreadcrumbItemType[] = [
{ label: module.name, href: module.href, icon: module.icon },
...breadcrumbs,
]

const hasNavigation = breadcrumbs.length > 0

return (
<div
className={cn(
"flex h-16 items-center justify-between rounded-t-lg bg-f1-background/80 p-4 backdrop-blur-xl",
!hasNavigation &&
"border-b border-dashed border-transparent border-b-f1-border/80"
)}
>
<div className="flex items-center gap-3">
{menu.show && (
<Button
variant="ghost"
hideLabel
round
onClick={menu.onClick}
label="Menu"
icon={AlignTextJustify}
/>
)}
{!hasNavigation && <ModuleAvatar icon={module.icon} size="lg" />}
{breadcrumbsTree.length > 1 ? (
<Breadcrumbs breadcrumbs={breadcrumbsTree} />
) : (
<div className="text-xl font-semibold">{module.name}</div>
)}
</div>
{actions && (
<div className="flex items-center gap-2">
{actions.map((action, index) => (
<Button
hideLabel
round
key={index}
variant="outline"
onClick={action.onClick}
label={action.label}
icon={action.icon}
/>
))}
</div>
)}
</div>
)
}
2 changes: 2 additions & 0 deletions lib/experimental/Navigation/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./Breadcrumbs"
export * from "./Header"
1 change: 1 addition & 0 deletions lib/experimental/Navigation/exports.tsx
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./Header"
export * from "./Sidebar"
export * from "./Tabs"
13 changes: 5 additions & 8 deletions lib/ui/breadcrumb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const BreadcrumbList = React.forwardRef<
<ol
ref={ref}
className={cn(
"flex list-none flex-wrap items-center gap-1.5 break-words text-sm text-f1-foreground-secondary",
"flex list-none flex-wrap items-center gap-1 break-words text-f1-foreground-secondary",
className
)}
{...props}
Expand All @@ -33,7 +33,7 @@ const BreadcrumbItem = React.forwardRef<
>(({ className, ...props }, ref) => (
<li
ref={ref}
className={cn("inline-flex items-center gap-1.5", className)}
className={cn("inline-flex items-center gap-0.5", className)}
{...props}
/>
))
Expand All @@ -51,7 +51,7 @@ const BreadcrumbLink = React.forwardRef<
<Comp
ref={ref}
className={cn(
"text-f1-foreground no-underline transition-colors hover:text-f1-foreground",
"rounded-sm px-1.5 py-0.5 font-medium text-f1-foreground no-underline transition-colors hover:bg-f1-background-secondary",
className
)}
{...props}
Expand All @@ -69,7 +69,7 @@ const BreadcrumbPage = React.forwardRef<
role="link"
aria-disabled="true"
aria-current="page"
className={cn("text-f1-foreground", className)}
className={cn("px-1.5 py-0.5 text-f1-foreground", className)}
{...props}
/>
))
Expand All @@ -83,10 +83,7 @@ const BreadcrumbSeparator = ({
<li
role="presentation"
aria-hidden="true"
className={cn(
"flex align-bottom text-f1-foreground [&>svg]:size-3.5",
className
)}
className={cn("flex align-bottom", className)}
{...props}
>
{children ?? <ChevronRight />}
Expand Down

0 comments on commit 4feb3d2

Please sign in to comment.