Files
healthy-fit/frontend/src/components/catalyst/navbar.tsx
Carlos Escalante bd91eb4171 Migrate frontend to TypeScript and extend user profile
Converted frontend codebase from JavaScript to TypeScript, including pages, components, and context. Added new layout and UI kit components. Updated backend user model and schemas to support profile fields (firstname, lastname, age, gender, height, weight, unit_preference) and added endpoints for reading/updating current user. Introduced food log listing endpoint and migration script for user table. Updated dependencies and build configs for TypeScript and Tailwind v4.
2026-01-18 19:01:00 -06:00

97 lines
3.9 KiB
TypeScript

'use client'
import * as Headless from '@headlessui/react'
import clsx from 'clsx'
import { LayoutGroup, motion } from 'motion/react'
import React, { forwardRef, useId } from 'react'
import { TouchTarget } from './button'
import { Link } from './link'
export function Navbar({ className, ...props }: React.ComponentPropsWithoutRef<'nav'>) {
return <nav {...props} className={clsx(className, 'flex flex-1 items-center gap-4 py-2.5')} />
}
export function NavbarDivider({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
return <div aria-hidden="true" {...props} className={clsx(className, 'h-6 w-px bg-zinc-950/10 dark:bg-white/10')} />
}
export function NavbarSection({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
let id = useId()
return (
<LayoutGroup id={id}>
<div {...props} className={clsx(className, 'flex items-center gap-3')} />
</LayoutGroup>
)
}
export function NavbarSpacer({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
return <div aria-hidden="true" {...props} className={clsx(className, '-ml-4 flex-1')} />
}
export const NavbarItem = forwardRef(function NavbarItem(
{
current,
className,
children,
...props
}: { current?: boolean; className?: string; children: React.ReactNode } & (
| ({ href?: never } & Omit<Headless.ButtonProps, 'as' | 'className'>)
| ({ href: string } & Omit<React.ComponentPropsWithoutRef<typeof Link>, 'className'>)
),
ref: React.ForwardedRef<HTMLAnchorElement | HTMLButtonElement>
) {
let classes = clsx(
// Base
'relative flex min-w-0 items-center gap-3 rounded-lg p-2 text-left text-base/6 font-medium text-zinc-950 sm:text-sm/5',
// Leading icon/icon-only
'*:data-[slot=icon]:size-6 *:data-[slot=icon]:shrink-0 *:data-[slot=icon]:fill-zinc-500 sm:*:data-[slot=icon]:size-5',
// Trailing icon (down chevron or similar)
'*:not-nth-2:last:data-[slot=icon]:ml-auto *:not-nth-2:last:data-[slot=icon]:size-5 sm:*:not-nth-2:last:data-[slot=icon]:size-4',
// Avatar
'*:data-[slot=avatar]:-m-0.5 *:data-[slot=avatar]:size-7 *:data-[slot=avatar]:[--avatar-radius:var(--radius-md)] sm:*:data-[slot=avatar]:size-6',
// Hover
'data-hover:bg-zinc-950/5 data-hover:*:data-[slot=icon]:fill-zinc-950',
// Active
'data-active:bg-zinc-950/5 data-active:*:data-[slot=icon]:fill-zinc-950',
// Dark mode
'dark:text-white dark:*:data-[slot=icon]:fill-zinc-400',
'dark:data-hover:bg-white/5 dark:data-hover:*:data-[slot=icon]:fill-white',
'dark:data-active:bg-white/5 dark:data-active:*:data-[slot=icon]:fill-white'
)
return (
<span className={clsx(className, 'relative')}>
{current && (
<motion.span
layoutId="current-indicator"
className="absolute inset-x-2 -bottom-2.5 h-0.5 rounded-full bg-zinc-950 dark:bg-white"
/>
)}
{typeof props.href === 'string' ? (
<Link
{...props}
className={classes}
data-current={current ? 'true' : undefined}
ref={ref as React.ForwardedRef<HTMLAnchorElement>}
>
<TouchTarget>{children}</TouchTarget>
</Link>
) : (
<Headless.Button
{...props}
className={clsx('cursor-default', classes)}
data-current={current ? 'true' : undefined}
ref={ref}
>
<TouchTarget>{children}</TouchTarget>
</Headless.Button>
)}
</span>
)
})
export function NavbarLabel({ className, ...props }: React.ComponentPropsWithoutRef<'span'>) {
return <span {...props} className={clsx(className, 'truncate')} />
}