diff --git a/.changeset/tangy-groups-listen.md b/.changeset/tangy-groups-listen.md
new file mode 100644
index 0000000..2c1cbf6
--- /dev/null
+++ b/.changeset/tangy-groups-listen.md
@@ -0,0 +1,5 @@
+---
+'commerce-toolkit': minor
+---
+
+Added Breadcrumbs component
diff --git a/.cursor/commands/tailwind-downgrade.md b/.cursor/commands/tailwind-downgrade.md
index 7abd7f3..652db86 100644
--- a/.cursor/commands/tailwind-downgrade.md
+++ b/.cursor/commands/tailwind-downgrade.md
@@ -27,13 +27,13 @@ Below is an example:
- `
` → ``
-Sometimes you may come across examples that use a fallback value for CSS variables. When that's the case, you'll need to change "()" with "[]" and also wrap ANY CSS variable fallback with the hsl color function (this applies to all color variables like `--background`, `--foreground`, `--contrast-*`, etc.).
+Sometimes you may come across examples that use a fallback value for CSS variables. When that's the case, you'll need to change "()" with "[]".
Below are examples:
-- `bg-(--dropdown-menu-background,var(--background))` → `bg-[var(--dropdown-menu-background,hsl(var(--background)))]`
-- `border-(--dropdown-menu-border,var(--contrast-100))` → `border-[var(--dropdown-menu-border,hsl(var(--contrast-100)))]`
-- `text-(--custom-text,var(--foreground))` → `text-[var(--custom-text,hsl(var(--foreground)))]`
+- `bg-(--dropdown-menu-background,var(--background))` → `bg-[var(--dropdown-menu-background,var(--background))]`
+- `border-(--dropdown-menu-border,var(--contrast-100))` → `border-[var(--dropdown-menu-border,var(--contrast-100))]`
+- `text-(--custom-text,var(--foreground))` → `text-[var(--custom-text,var(--foreground))]`
## Highlight and Shadow variants
@@ -50,5 +50,5 @@ The following CSS variables are included in the Tailwind 4 version:
However, in Tailwind 3 we do not have these CSS variables defined. Therefore, when you come across a class name that uses one of these CSS variables it needs to be converted for Tailwind 3 like this:
-- `bg-(--alert-success-background,var(--success-highlight))` → `bg-[var(--alert-success-background,color-mix(in_oklab,_hsl(var(--success)),_white_75%))]`
-- `text-(--form-status-light-text-success,var(--success-shadow))` → `text-[var(--form-status-light-text-success,color-mix(in_oklab,hsl(var(--success)),black_75%))]`
+- `bg-(--alert-success-background,var(--success-highlight))` → `bg-[var(--alert-success-background,color-mix(in_oklab,var(--success),white_75%))]`
+- `text-(--form-status-light-text-success,var(--success-shadow))` → `text-[var(--form-status-light-text-success,color-mix(in_oklab,var(--success),black_75%))]`
diff --git a/package.json b/package.json
index 32273e5..cd01fd3 100644
--- a/package.json
+++ b/package.json
@@ -47,6 +47,16 @@
"default": "./dist/banner.cjs"
}
},
+ "./breadcrumbs": {
+ "import": {
+ "types": "./dist/components/breadcrumbs/primitives.d.ts",
+ "default": "./dist/breadcrumbs.js"
+ },
+ "require": {
+ "types": "./dist/components/breadcrumbs/primitives.d.ts",
+ "default": "./dist/breadcrumbs.cjs"
+ }
+ },
"./button-radio-group": {
"import": {
"types": "./dist/components/button-radio-group/primitives.d.ts",
diff --git a/src/components/accordion/archive/archive-accordion.tsx b/src/components/accordion/archive/archive-accordion.tsx
deleted file mode 100644
index e8070af..0000000
--- a/src/components/accordion/archive/archive-accordion.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-'use client';
-
-import * as AccordionPrimitive from '@radix-ui/react-accordion';
-import { clsx } from 'clsx';
-import { ComponentPropsWithoutRef, useEffect, useState } from 'react';
-
-export interface AccordionProps extends ComponentPropsWithoutRef {
- colorScheme?: 'light' | 'dark';
-}
-
-/**
- * This component supports various CSS variables for theming. Here's a comprehensive list, along
- * with their default values:
- *
- * ```css
- * :root {
- * --accordion-focus: var(--primary);
- * --acordion-light-offset: var(--background);
- * --accordion-light-title-text: var(--contrast-400);
- * --accordion-light-title-text-hover: var(--foreground);
- * --accordion-light-title-icon: var(--contrast-500);
- * --accordion-light-title-icon-hover: var(--foreground);
- * --accordion-light-content-text: var(--foreground);
- * --acordion-dark-offset: var(--foreground);
- * --accordion-dark-title-text: var(--contrast-200);
- * --accordion-dark-title-text-hover: var(--background);
- * --accordion-dark-title-icon: var(--contrast-200);
- * --accordion-dark-title-icon-hover: var(--background);
- * --accordion-dark-content-text: var(--background);
- * --accordion-title-font-family: var(--font-family-mono);
- * --accordion-content-font-family: var(--font-family-body);
- * }
- * ```
- */
-function AccordionItem({
- title,
- children,
- colorScheme = 'light',
- className,
- ...props
-}: AccordionProps) {
- const [isMounted, setIsMounted] = useState(false);
-
- useEffect(() => {
- setIsMounted(true);
- }, []);
-
- return (
-
-
-
-
- {title}
-
-
-
-
-
-
- {children}
-
-
-
- );
-}
-
-function AnimatedChevron({
- className,
- ...props
-}: React.JSX.IntrinsicAttributes & React.SVGProps) {
- return (
-
- );
-}
-
-const Accordion = AccordionPrimitive.Root;
-
-export { Accordion, AccordionItem };
diff --git a/src/components/breadcrumbs/breadcrumbs.tsx b/src/components/breadcrumbs/breadcrumbs.tsx
new file mode 100644
index 0000000..6f87c46
--- /dev/null
+++ b/src/components/breadcrumbs/breadcrumbs.tsx
@@ -0,0 +1,69 @@
+import type { ReactNode } from 'react';
+
+import { AnimatedUnderline } from '@/components/animated-underline';
+import * as BreadcrumbsPrimitive from '@/components/breadcrumbs';
+import { cn } from '@/lib';
+
+interface Breadcrumb {
+ label: string;
+ href: string;
+ asChild?: boolean;
+ children?: ReactNode;
+}
+
+export interface BreadcrumbsProps {
+ breadcrumbs: Breadcrumb[];
+ className?: string;
+ ariaLabel?: string;
+ icon?: {
+ asChild?: boolean;
+ children?: ReactNode;
+ };
+}
+
+/**
+ * This component supports various CSS variables for theming. Here's a comprehensive list, along
+ * with their default values:
+ *
+ * ```css
+ * :root {
+ * --breadcrumbs-font-family: var(--font-family-body);
+ * --breadcrumbs-primary-text: var(--foreground);
+ * --breadcrumbs-secondary-text: var(--contrast-500);
+ * --breadcrumbs-icon: var(--contrast-500);
+ * }
+ * ```
+ */
+export function Breadcrumbs({
+ breadcrumbs,
+ className,
+ ariaLabel = 'Breadcrumb',
+ icon,
+}: BreadcrumbsProps) {
+ return (
+
+
+ {breadcrumbs.map(({ label, href, asChild, children }, index) => {
+ if (index < breadcrumbs.length - 1) {
+ return (
+
+
+ {asChild === true ? children : {label}}
+
+
+ {icon?.children}
+
+
+ );
+ }
+
+ return (
+
+ {label}
+
+ );
+ })}
+
+
+ );
+}
diff --git a/src/components/breadcrumbs/index.ts b/src/components/breadcrumbs/index.ts
new file mode 100644
index 0000000..b76ad83
--- /dev/null
+++ b/src/components/breadcrumbs/index.ts
@@ -0,0 +1,2 @@
+export { Breadcrumbs, type BreadcrumbsProps } from '@/components/breadcrumbs/breadcrumbs';
+export * from '@/components/breadcrumbs/primitives';
diff --git a/src/components/breadcrumbs/primitives.ts b/src/components/breadcrumbs/primitives.ts
new file mode 100644
index 0000000..46624a3
--- /dev/null
+++ b/src/components/breadcrumbs/primitives.ts
@@ -0,0 +1,24 @@
+export {
+ BreadcrumbsRoot as Root,
+ type BreadcrumbsRootProps as RootProps,
+} from '@/components/breadcrumbs/primitives/breadcrumbs-root';
+export {
+ BreadcrumbsList as List,
+ type BreadcrumbsListProps as ListProps,
+} from '@/components/breadcrumbs/primitives/breadcrumbs-list';
+export {
+ BreadcrumbsItem as Item,
+ type BreadcrumbsItemProps as ItemProps,
+} from '@/components/breadcrumbs/primitives/breadcrumbs-item';
+export {
+ BreadcrumbsLink as Link,
+ type BreadcrumbsLinkProps as LinkProps,
+} from '@/components/breadcrumbs/primitives/breadcrumbs-link';
+export {
+ BreadcrumbsIcon as Icon,
+ type BreadcrumbsIconProps as IconProps,
+} from '@/components/breadcrumbs/primitives/breadcrumbs-icon';
+export {
+ BreadcrumbsCurrent as Current,
+ type BreadcrumbsCurrentProps as CurrentProps,
+} from '@/components/breadcrumbs/primitives/breadcrumbs-current';
diff --git a/src/components/breadcrumbs/primitives/breadcrumbs-current.tsx b/src/components/breadcrumbs/primitives/breadcrumbs-current.tsx
new file mode 100644
index 0000000..4a1d056
--- /dev/null
+++ b/src/components/breadcrumbs/primitives/breadcrumbs-current.tsx
@@ -0,0 +1,20 @@
+import type { ComponentProps } from 'react';
+
+import { cn } from '@/lib';
+
+export type BreadcrumbsCurrentProps = ComponentProps<'span'>;
+
+export function BreadcrumbsCurrent({ className, children, ...props }: BreadcrumbsCurrentProps) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/components/breadcrumbs/primitives/breadcrumbs-icon.tsx b/src/components/breadcrumbs/primitives/breadcrumbs-icon.tsx
new file mode 100644
index 0000000..c490519
--- /dev/null
+++ b/src/components/breadcrumbs/primitives/breadcrumbs-icon.tsx
@@ -0,0 +1,37 @@
+'use client';
+
+import { Slot } from '@radix-ui/react-slot';
+import { ChevronRight } from 'lucide-react';
+import type { ReactNode } from 'react';
+
+import { cn } from '@/lib';
+
+export interface BreadcrumbsIconProps {
+ asChild?: boolean;
+ className?: string;
+ children?: ReactNode;
+}
+
+export function BreadcrumbsIcon({ asChild = false, className, children }: BreadcrumbsIconProps) {
+ if (asChild) {
+ return (
+
+ {children}
+
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/src/components/breadcrumbs/primitives/breadcrumbs-item.tsx b/src/components/breadcrumbs/primitives/breadcrumbs-item.tsx
new file mode 100644
index 0000000..0fa8732
--- /dev/null
+++ b/src/components/breadcrumbs/primitives/breadcrumbs-item.tsx
@@ -0,0 +1,17 @@
+import type { ComponentProps } from 'react';
+
+import { cn } from '@/lib';
+
+export type BreadcrumbsItemProps = ComponentProps<'li'>;
+
+export function BreadcrumbsItem({ className, children, ...props }: BreadcrumbsItemProps) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/components/breadcrumbs/primitives/breadcrumbs-link.tsx b/src/components/breadcrumbs/primitives/breadcrumbs-link.tsx
new file mode 100644
index 0000000..37b15d4
--- /dev/null
+++ b/src/components/breadcrumbs/primitives/breadcrumbs-link.tsx
@@ -0,0 +1,27 @@
+'use client';
+
+import { Slot } from '@radix-ui/react-slot';
+import type { ComponentProps } from 'react';
+
+import { cn } from '@/lib';
+
+export interface BreadcrumbsLinkProps extends ComponentProps<'a'> {
+ asChild?: boolean;
+}
+
+export function BreadcrumbsLink({ asChild = false, className, ...props }: BreadcrumbsLinkProps) {
+ const Component = asChild ? Slot : 'a';
+
+ return (
+
+ );
+}
diff --git a/src/components/breadcrumbs/primitives/breadcrumbs-list.tsx b/src/components/breadcrumbs/primitives/breadcrumbs-list.tsx
new file mode 100644
index 0000000..5dc50f8
--- /dev/null
+++ b/src/components/breadcrumbs/primitives/breadcrumbs-list.tsx
@@ -0,0 +1,20 @@
+import type { ComponentProps } from 'react';
+
+import { cn } from '@/lib';
+
+export type BreadcrumbsListProps = ComponentProps<'ol'>;
+
+export function BreadcrumbsList({ className, children, ...props }: BreadcrumbsListProps) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/components/breadcrumbs/primitives/breadcrumbs-root.tsx b/src/components/breadcrumbs/primitives/breadcrumbs-root.tsx
new file mode 100644
index 0000000..cb24a01
--- /dev/null
+++ b/src/components/breadcrumbs/primitives/breadcrumbs-root.tsx
@@ -0,0 +1,13 @@
+import type { ComponentProps } from 'react';
+
+import { cn } from '@/lib';
+
+export type BreadcrumbsRootProps = ComponentProps<'nav'>;
+
+export function BreadcrumbsRoot({ className, children, ...props }: BreadcrumbsRootProps) {
+ return (
+
+ );
+}
diff --git a/src/components/breadcrumbs/storybook/breadcrumbs.stories.tsx b/src/components/breadcrumbs/storybook/breadcrumbs.stories.tsx
new file mode 100644
index 0000000..8d4267e
--- /dev/null
+++ b/src/components/breadcrumbs/storybook/breadcrumbs.stories.tsx
@@ -0,0 +1,186 @@
+import type { Meta, StoryObj } from '@storybook/react-vite';
+import { Dot } from 'lucide-react';
+import type { ComponentType } from 'react';
+
+import { AnimatedUnderline } from '@/components/animated-underline';
+import { Breadcrumbs, type BreadcrumbsProps } from '@/components/breadcrumbs';
+import * as BreadcrumbsPrimitive from '@/components/breadcrumbs/primitives';
+
+const meta: Meta = {
+ title: 'Components/Breadcrumbs',
+ component: Breadcrumbs,
+ parameters: {
+ layout: 'padded',
+ docs: {
+ description: {
+ component: `
+A navigation component that displays the current page's location within a hierarchy, helping users understand their position and navigate back through parent pages.
+
+## CSS Variables
+
+\`\`\`css
+:root {
+ --breadcrumbs-font-family: var(--font-family-body);
+ --breadcrumbs-primary-text: var(--foreground);
+ --breadcrumbs-secondary-text: var(--contrast-500);
+ --breadcrumbs-icon: var(--contrast-500);
+}
+\`\`\`
+
+## Usage
+
+### High-Level Component
+
+The \`Breadcrumbs\` component provides a simple API for breadcrumb navigation:
+
+\`\`\`tsx
+import { Breadcrumbs } from '@/components/breadcrumbs';
+
+
+\`\`\`
+
+### Composable Anatomy
+
+For more control, use the primitive components directly:
+
+\`\`\`tsx
+import * as Breadcrumbs from '@/components/breadcrumbs';
+import { AnimatedUnderline } from '@/components/animated-underline';
+
+
+
+
+
+ Home
+
+
+
+
+
+ Products
+
+
+
+
+ Cleaning Supplies
+
+
+
+\`\`\`
+ `,
+ },
+ },
+ },
+ tags: ['autodocs'],
+ argTypes: {
+ breadcrumbs: {
+ control: false,
+ description: 'Array of breadcrumb items with label and href',
+ },
+ ariaLabel: {
+ control: 'text',
+ description: 'Accessible label for the navigation landmark',
+ },
+ className: {
+ control: 'text',
+ description: 'Additional CSS classes to apply',
+ },
+ },
+ decorators: [
+ (Story: ComponentType) => (
+
+
+
+ ),
+ ],
+};
+
+export default meta;
+type Story = StoryObj;
+
+// Default breadcrumbs
+export const Default: Story = {
+ args: {
+ breadcrumbs: [
+ { label: 'Home', href: '/' },
+ { label: 'Products', href: '/products' },
+ { label: 'Cleaning Supplies', href: '/products/cleaning' },
+ ],
+ ariaLabel: 'Breadcrumb',
+ },
+};
+
+// Longer breadcrumb trail
+export const LongPath: Story = {
+ args: {
+ breadcrumbs: [
+ { label: 'Home', href: '/' },
+ { label: 'Products', href: '/products' },
+ { label: 'Kitchen', href: '/products/kitchen' },
+ { label: 'Cleaning', href: '/products/kitchen/cleaning' },
+ { label: 'Natural Fiber Scrub Brush', href: '/products/kitchen/cleaning/scrub-brush' },
+ ],
+ ariaLabel: 'Breadcrumb',
+ },
+};
+
+// Composable anatomy example
+export const ComposableAnatomy: Story = {
+ render: () => (
+
+
+
+
+ Home
+
+
+
+
+
+ Products
+
+
+
+
+ Eco Dish Sponge Set
+
+
+
+ ),
+};
+
+// Custom separator icon
+export const CustomSeparator: Story = {
+ render: () => (
+
+
+
+
+ Home
+
+
+
+
+
+
+
+ Categories
+
+
+
+
+
+
+ Bathroom
+
+
+
+ ),
+};