Skip to content

Commit

Permalink
Added Breadcrumb component
Browse files Browse the repository at this point in the history
  • Loading branch information
jsimck committed Mar 12, 2024
1 parent b00d838 commit 5cc6b54
Show file tree
Hide file tree
Showing 62 changed files with 480 additions and 307 deletions.
5 changes: 5 additions & 0 deletions .changeset/afraid-parrots-own.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@utima/ui": minor
---

Added Breadcrumb component, performance optimizations
4 changes: 2 additions & 2 deletions packages/ui/src/components/aspectRatio/AspectRatio.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Root } from '@radix-ui/react-aspect-ratio';
import { Slot } from '@radix-ui/react-slot';
import type { ComponentProps } from 'react';
import type { ComponentPropsWithoutRef } from 'react';

import { aspectRatioDef } from './AspectRatio.styles';

export function AspectRatio({
children,
...restProps
}: ComponentProps<typeof Root>) {
}: ComponentPropsWithoutRef<typeof Root>) {
return (
<Root {...restProps}>
<Slot className={aspectRatioDef.slot}>{children}</Slot>
Expand Down
23 changes: 12 additions & 11 deletions packages/ui/src/components/avatar/Avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,18 @@ import { avatarStyles } from './Avatar.styles';
import { AvatarFallback } from './AvatarFallback';
import { AvatarImage } from './AvatarImage';

type AvatarProps = ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> &
Pick<
ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>,
'onLoadingStatusChange'
> &
VariantProps<typeof avatarStyles> & {
delayMs?: number;
fallback?: string;
src?: string;
alt?: string;
};
interface AvatarProps
extends ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>,
Pick<
ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>,
'onLoadingStatusChange'
>,
VariantProps<typeof avatarStyles> {
delayMs?: number;
fallback?: string;
src?: string;
alt?: string;
}

export const Avatar = forwardRef<
ElementRef<typeof AvatarPrimitive.Root>,
Expand Down
39 changes: 18 additions & 21 deletions packages/ui/src/components/badge/Badge.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,27 @@
import type { VariantProps } from 'class-variance-authority';
import { forwardRef, type ComponentProps } from 'react';
import { type ComponentPropsWithoutRef, memo } from 'react';

import { cn } from '@/utils';

import { badgeStyles } from './Badge.styles';

export type BadgeProps = ComponentProps<'span'> &
Omit<VariantProps<typeof badgeStyles>, 'outline' | 'disabled'> & {
outline?: boolean;
disabled?: boolean;
};
export interface BadgeProps
extends ComponentPropsWithoutRef<'span'>,
Omit<VariantProps<typeof badgeStyles>, 'outline' | 'disabled'> {
outline?: boolean;
disabled?: boolean;
}

export const Badge = forwardRef<HTMLDivElement, BadgeProps>(
(
{
className,
variant = 'primary',
size = 'md',
outline = false,
disabled = false,
...restProps
},
ref,
) => (
export const Badge = memo(function Badge({
className,
variant = 'primary',
size = 'md',
outline = false,
disabled = false,
...restProps
}: BadgeProps) {
return (
<span
ref={ref}
className={cn(
badgeStyles({
variant,
Expand All @@ -36,5 +33,5 @@ export const Badge = forwardRef<HTMLDivElement, BadgeProps>(
)}
{...restProps}
/>
),
);
);
});
52 changes: 52 additions & 0 deletions packages/ui/src/components/breadcrumb/Breadcrumb.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type { Meta, StoryObj } from '@storybook/react';
import { Slash } from 'lucide-react';

import * as Breadcrumb from './index';
import * as Dropdown from '../dropdown/index';

const meta: Meta<typeof Breadcrumb.Root> = {
component: Breadcrumb.Root,
tags: ['autodocs'],
title: 'Components/Breadcrumb',
args: {
children: (
<Breadcrumb.List>
<Breadcrumb.Item>
<Breadcrumb.Link href='/'>Home</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Dropdown.Root>
<Dropdown.Trigger className='flex items-center gap-1'>
<Breadcrumb.Ellipsis className='h-4 w-4' />
<span className='sr-only'>Toggle menu</span>
</Dropdown.Trigger>
<Dropdown.Content align='start'>
<Dropdown.Item>Documentation</Dropdown.Item>
<Dropdown.Item>Themes</Dropdown.Item>
<Dropdown.Item>GitHub</Dropdown.Item>
</Dropdown.Content>
</Dropdown.Root>
</Breadcrumb.Item>
<Breadcrumb.Separator>
<Slash />
</Breadcrumb.Separator>
<Breadcrumb.Item>
<Breadcrumb.Link href='/docs/components'>Components</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Page>Breadcrumb</Breadcrumb.Page>
</Breadcrumb.Item>
</Breadcrumb.List>
),
},
};

export default meta;

type Story = StoryObj<typeof Breadcrumb.Root>;

export const Basic: Story = {
args: {},
};
19 changes: 19 additions & 0 deletions packages/ui/src/components/breadcrumb/Breadcrumb.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { twOverrides } from '@/overrides';

import { globalDef } from '../global.styles';

export const breadcrumbDef = twOverrides(
{
breadcrumb: '',
ellipsis: {
wrapper: `${globalDef.ringVisible} flex h-9 w-9 items-center justify-center`,
icon: 'size-4',
},
item: 'inline-flex items-center gap-1.5',
link: `${globalDef.ringVisible} rounded-radius transition-colors hover:text-foreground`,
list: 'flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-fg sm:gap-2.5',
page: 'font-normal text-foreground',
separator: '[&>svg]:size-3.5',
},
'breadcrumb',
);
22 changes: 22 additions & 0 deletions packages/ui/src/components/breadcrumb/Breadcrumb.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { type ComponentPropsWithoutRef, type ReactNode, memo } from 'react';

import { cn } from '@/utils';

import { breadcrumbDef } from './Breadcrumb.styles';

interface BreadcrumbProps extends ComponentPropsWithoutRef<'nav'> {
separator?: ReactNode;
}

export const Breadcrumb = memo(function Breadcrumb({
className,
...props
}: BreadcrumbProps) {
return (
<nav
aria-label='breadcrumb'
className={cn(breadcrumbDef.breadcrumb, className)}
{...props}
/>
);
});
24 changes: 24 additions & 0 deletions packages/ui/src/components/breadcrumb/BreadcrumbEllipsis.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { MoreHorizontal } from 'lucide-react';
import { memo, type ComponentPropsWithoutRef } from 'react';

import { cn } from '@/utils';

import { breadcrumbDef } from './Breadcrumb.styles';

export const BreadcrumbEllipsis = memo(function BreadcrumbEllipsis({
className,
children,
...props
}: ComponentPropsWithoutRef<'span'>) {
return (
<span
role='presentation'
aria-hidden='true'
className={cn(breadcrumbDef.ellipsis.wrapper, className)}
{...props}
>
<MoreHorizontal className={breadcrumbDef.ellipsis.icon} />
<span className='sr-only'>More</span>
</span>
);
});
12 changes: 12 additions & 0 deletions packages/ui/src/components/breadcrumb/BreadcrumbItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { memo, type ComponentPropsWithoutRef } from 'react';

import { cn } from '@/utils';

import { breadcrumbDef } from './Breadcrumb.styles';

export const BreadcrumbItem = memo(function BreadcrumbItem({
className,
...props
}: ComponentPropsWithoutRef<'li'>) {
return <li className={cn(breadcrumbDef.item, className)} {...props} />;
});
20 changes: 20 additions & 0 deletions packages/ui/src/components/breadcrumb/BreadcrumbLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Slot } from '@radix-ui/react-slot';
import { memo, type ComponentPropsWithoutRef } from 'react';

import { cn } from '@/utils';

import { breadcrumbDef } from './Breadcrumb.styles';

interface BreadcrumbLinkProps extends ComponentPropsWithoutRef<'a'> {
asChild?: boolean;
}

export const BreadcrumbLink = memo(function BreadcrumbLink({
asChild,
className,
...props
}: BreadcrumbLinkProps) {
const Comp = asChild ? Slot : 'a';

return <Comp className={cn(breadcrumbDef.link, className)} {...props} />;
});
12 changes: 12 additions & 0 deletions packages/ui/src/components/breadcrumb/BreadcrumbList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { memo, type ComponentPropsWithoutRef } from 'react';

import { cn } from '@/utils';

import { breadcrumbDef } from './Breadcrumb.styles';

export const BreadcrumbList = memo(function BreadcrumbList({
className,
...props
}: ComponentPropsWithoutRef<'ol'>) {
return <ol className={cn(breadcrumbDef.list, className)} {...props} />;
});
20 changes: 20 additions & 0 deletions packages/ui/src/components/breadcrumb/BreadcrumbPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { memo, type ComponentPropsWithoutRef } from 'react';

import { cn } from '@/utils';

import { breadcrumbDef } from './Breadcrumb.styles';

export const BreadcrumbPage = memo(function BreadcrumbPage({
className,
...props
}: ComponentPropsWithoutRef<'span'>) {
return (
<span
role='link'
aria-disabled='true'
aria-current='page'
className={cn(breadcrumbDef.page, className)}
{...props}
/>
);
});
23 changes: 23 additions & 0 deletions packages/ui/src/components/breadcrumb/BreadcrumbSeparator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ChevronRight } from 'lucide-react';
import { memo, type ComponentPropsWithoutRef } from 'react';

import { cn } from '@/utils';

import { breadcrumbDef } from './Breadcrumb.styles';

export const BreadcrumbSeparator = memo(function BreadcrumbSeparator({
className,
children,
...props
}: ComponentPropsWithoutRef<'li'>) {
return (
<li
role='presentation'
aria-hidden='true'
className={cn(breadcrumbDef.separator, className)}
{...props}
>
{children ?? <ChevronRight />}
</li>
);
});
7 changes: 7 additions & 0 deletions packages/ui/src/components/breadcrumb/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export { Breadcrumb as Root } from './Breadcrumb';
export { BreadcrumbLink as Link } from './BreadcrumbLink';
export { BreadcrumbEllipsis as Ellipsis } from './BreadcrumbEllipsis';
export { BreadcrumbItem as Item } from './BreadcrumbItem';
export { BreadcrumbList as List } from './BreadcrumbList';
export { BreadcrumbPage as Page } from './BreadcrumbPage';
export { BreadcrumbSeparator as Separator } from './BreadcrumbSeparator';
17 changes: 9 additions & 8 deletions packages/ui/src/components/button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import { cn } from '@/utils';

import { buttonStyles } from './Button.styles';

type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> &
Omit<VariantProps<typeof buttonStyles>, 'outline'> & {
asChild?: boolean;
outline?: boolean;
loading?: boolean;
icon?: ReactNode;
iconSize?: number;
};
interface ButtonProps
extends ButtonHTMLAttributes<HTMLButtonElement>,
Omit<VariantProps<typeof buttonStyles>, 'outline'> {
asChild?: boolean;
outline?: boolean;
loading?: boolean;
icon?: ReactNode;
iconSize?: number;
}

export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
(
Expand Down
5 changes: 3 additions & 2 deletions packages/ui/src/components/checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import { cn } from '@/utils';

import { checkboxDef, checkboxStyles } from './Checkbox.styles';

type CheckboxProps = ComponentPropsWithoutRef<typeof Root> &
Omit<VariantProps<typeof checkboxStyles>, 'thumbSize'>;
interface CheckboxProps
extends ComponentPropsWithoutRef<typeof Root>,
Omit<VariantProps<typeof checkboxStyles>, 'thumbSize'> {}

export const Checkbox = forwardRef<ElementRef<typeof Root>, CheckboxProps>(
({ className, variant = 'primary', size = 'md', ...restProps }, ref) => (
Expand Down
4 changes: 1 addition & 3 deletions packages/ui/src/components/command/Command.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@ import { cn } from '@/utils';

import { commandDef } from './Command.styles';

type CommandProps = ComponentPropsWithoutRef<typeof CommandPrimitive>;

export const Command = forwardRef<
ElementRef<typeof CommandPrimitive>,
CommandProps
ComponentPropsWithoutRef<typeof CommandPrimitive>
>(({ className, ...restProps }, ref) => (
<CommandPrimitive
ref={ref}
Expand Down
Loading

0 comments on commit 5cc6b54

Please sign in to comment.