Files
ermnvldmr.com/packages/ui/src/components/Breadcrumbs/Breadcrumbs.tsx
T
deytenit b84ef775b8 Various build shenenigans (#5)
This PR extends the build pipeline of the www service to support
locale-prefixed static builds (EN/RU) with build-time SSG output
injection, while also refactoring UI typography to a tokenized Tailwind
v4 @theme-driven approach.

Changes:

- Add build-time SSG plugin to @ermnvldmr/rsbuild-config and wire
multi-locale builds (/en/, /ru/) into services/www.
- Introduce new @ermnvldmr/i18n package (locale constants, path helpers,
keysets, pluralization) and apply it to www navigation/text.
- Refactor ui typography to a shared getTypographyClassNames utility +
Tailwind token definitions, with stl/cn updated for correct
Tailwind-merge behavior.
2026-03-16 00:00:20 +03:00

142 lines
3.6 KiB
TypeScript

import { localePath } from '@ermnvldmr/i18n';
import { cn } from '@ermnvldmr/stl';
import { ChevronRight } from 'lucide-react';
import React, { memo } from 'react';
import { HStack } from '../HStack/HStack';
import { Text } from '../Text/Text';
import type { HStackProps } from '../HStack/HStack';
import type { ClassNameProps, TestIdProps } from '@ermnvldmr/stl';
/**
* Props for the Breadcrumbs component.
*/
export interface BreadcrumbsProps extends HStackProps {
/** Breadcrumb items and separators */
children: React.ReactNode;
}
/**
* Root component for hierarchical navigation.
*/
const BreadcrumbsRoot = memo(function Breadcrumbs({ children, ...props }: BreadcrumbsProps) {
return (
<nav aria-label="Breadcrumb">
<HStack align="center" as="ol" gap={2} {...props}>
{children}
</HStack>
</nav>
);
});
/**
* Props for the individual Breadcrumbs item.
*/
export interface BreadcrumbsItemProps extends TestIdProps {
/** Content to be rendered inside the item */
children: React.ReactNode;
/** Optional className for the list item */
className?: string;
/** Whether this is the current page */
isCurrent?: boolean;
/** Optional href for the item (renders as a link) */
href?: string;
/** Optional click handler for the item */
onClick?: React.MouseEventHandler<HTMLElement>;
}
/**
* Individual step in the breadcrumb trail.
*/
const BreadcrumbsItem = memo(function BreadcrumbsItem({
children,
className,
isCurrent,
href,
onClick,
'data-testid': testId,
}: BreadcrumbsItemProps) {
const resolvedHref = localePath(href);
const isClickable = !!(resolvedHref ?? onClick);
return (
<li
aria-current={isCurrent ? 'page' : undefined}
className={cn(
'group flex items-center transition-all duration-200',
!isCurrent && isClickable && 'cursor-pointer',
className
)}
data-testid={testId}
>
<Text
as={resolvedHref ? 'a' : onClick ? 'button' : 'span'}
className={cn(
'transition-all duration-200',
!isCurrent && 'group-hover:text-[var(--rb-text)] group-hover:underline underline-offset-4'
)}
color={isCurrent ? 'default' : 'muted'}
href={resolvedHref}
size="m"
type="label"
onClick={onClick}
>
{children}
</Text>
</li>
);
});
/**
* Visual divider between breadcrumb items.
*/
export interface BreadcrumbsSeparatorProps extends ClassNameProps, TestIdProps {
/** Visual variant of the separator */
variant?: 'slash' | 'bull' | 'arrow';
}
const BreadcrumbsSeparator = memo(function BreadcrumbsSeparator({
variant = 'slash',
className,
'data-testid': testId,
}: BreadcrumbsSeparatorProps) {
const content = {
slash: '/',
bull: '•',
arrow: <ChevronRight className="size-4" />,
};
return (
<li
aria-hidden="true"
className={cn('text-[var(--rb-muted-text)]', className)}
data-testid={testId}
>
<Text color="muted" size="m" type="label">
{content[variant]}
</Text>
</li>
);
});
/**
* A hierarchical navigation component.
*
* Breadcrumbs provides a flexible way to display the current location within
* a site's hierarchy. It uses a compound component pattern for maximum flexibility.
*
* @example
* ```tsx
* <Breadcrumbs>
* <Breadcrumbs.Item><Link href="/">Home</Link></Breadcrumbs.Item>
* <Breadcrumbs.Separator variant="arrow" />
* <Breadcrumbs.Item>Current Page</Breadcrumbs.Item>
* </Breadcrumbs>
* ```
*/
export const Breadcrumbs = Object.assign(BreadcrumbsRoot, {
Item: BreadcrumbsItem,
Separator: BreadcrumbsSeparator,
});