Skip to content

Commit b893b16

Browse files
committed
breadcrumb added to docs site
1 parent 1524c66 commit b893b16

File tree

4 files changed

+514
-1
lines changed

4 files changed

+514
-1
lines changed
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
import React, { useState } from 'react';
2+
import { View, Text, Pressable, ViewProps, TextProps, PressableProps } from 'react-native';
3+
import Feather from 'react-native-vector-icons/Feather';
4+
import { cn } from '../../../lib/utils';
5+
6+
import {
7+
breadcrumbClassNames,
8+
breadcrumbListClassNames,
9+
breadcrumbItemClassNames,
10+
breadcrumbLinkClassNames,
11+
breadcrumbPageClassNames,
12+
breadcrumbSeparatorClassNames,
13+
breadcrumbEllipsisClassNames,
14+
} from './styles';
15+
16+
// Types for custom icon rendering
17+
type IconProps = {
18+
name: string;
19+
size: number;
20+
color: string;
21+
};
22+
23+
// Create a wrapper component to fix TypeScript compatibility issues
24+
const FeatherIcon = ({ name, size, color }: IconProps) => {
25+
// Use the two-step type assertion pattern (first to unknown, then to the desired type)
26+
// This is the recommended TypeScript pattern for type assertions when types don't overlap
27+
const IconComponent = Feather as unknown as React.FC<IconProps>;
28+
return <IconComponent name={name} size={size} color={color} />;
29+
};
30+
31+
// Main Breadcrumb container (replaces nav element)
32+
interface BreadcrumbProps extends ViewProps {
33+
className?: string;
34+
}
35+
36+
const Breadcrumb = ({ className, ...props }: BreadcrumbProps) => {
37+
return (
38+
<View
39+
accessible
40+
accessibilityRole="header"
41+
accessibilityLabel="breadcrumb"
42+
className={cn(breadcrumbClassNames.base, className)}
43+
{...props}
44+
/>
45+
);
46+
};
47+
48+
// BreadcrumbList (replaces ol element)
49+
interface BreadcrumbListProps extends ViewProps {
50+
className?: string;
51+
mode?: 'light' | 'dark';
52+
}
53+
54+
const BreadcrumbList = ({ className, mode = 'light', ...props }: BreadcrumbListProps) => {
55+
return (
56+
<View
57+
accessible
58+
className={cn(
59+
breadcrumbListClassNames.base,
60+
mode === 'dark' ? breadcrumbListClassNames.dark : breadcrumbListClassNames.light,
61+
className
62+
)}
63+
{...props}
64+
/>
65+
);
66+
};
67+
68+
// BreadcrumbItem (replaces li element)
69+
interface BreadcrumbItemProps extends ViewProps {
70+
className?: string;
71+
}
72+
73+
const BreadcrumbItem = ({ className, ...props }: BreadcrumbItemProps) => {
74+
return <View accessible className={cn(breadcrumbItemClassNames.base, className)} {...props} />;
75+
};
76+
77+
// BreadcrumbLink (replaces a element)
78+
interface BreadcrumbLinkProps extends PressableProps {
79+
className?: string;
80+
textClassName?: string;
81+
asChild?: boolean;
82+
mode?: 'light' | 'dark';
83+
}
84+
85+
const BreadcrumbLink = ({
86+
className,
87+
textClassName,
88+
asChild = false,
89+
children,
90+
mode = 'light',
91+
...props
92+
}: BreadcrumbLinkProps) => {
93+
const [isPressed, setIsPressed] = useState(false);
94+
95+
// If asChild is true and children is a valid element, we render the children directly
96+
if (asChild && React.isValidElement(children)) {
97+
const childElement = children as React.ReactElement<{ className?: string }>;
98+
return React.cloneElement(childElement, {
99+
...props,
100+
// Only add className if the child supports it
101+
...(childElement.props.className !== undefined && {
102+
className: cn(className, childElement.props.className),
103+
}),
104+
});
105+
}
106+
107+
return (
108+
<Pressable
109+
className={cn(className)}
110+
onPressIn={() => setIsPressed(true)}
111+
onPressOut={() => setIsPressed(false)}
112+
{...props}
113+
>
114+
{typeof children === 'string' ? (
115+
<Text
116+
className={cn(
117+
breadcrumbLinkClassNames.text.base,
118+
breadcrumbLinkClassNames.text[mode].default,
119+
isPressed && breadcrumbLinkClassNames.text[mode].pressed,
120+
textClassName
121+
)}
122+
>
123+
{children}
124+
</Text>
125+
) : (
126+
children
127+
)}
128+
</Pressable>
129+
);
130+
};
131+
132+
// BreadcrumbPage (replaces span for current page)
133+
interface BreadcrumbPageProps extends TextProps {
134+
className?: string;
135+
mode?: 'light' | 'dark';
136+
}
137+
138+
const BreadcrumbPage = ({ className, mode = 'light', ...props }: BreadcrumbPageProps) => {
139+
return (
140+
<Text
141+
accessibilityRole="text"
142+
className={cn(
143+
breadcrumbPageClassNames.base,
144+
mode === 'dark' ? breadcrumbPageClassNames.dark : breadcrumbPageClassNames.light,
145+
className
146+
)}
147+
{...props}
148+
/>
149+
);
150+
};
151+
152+
// BreadcrumbSeparator (replaces li for separator)
153+
interface BreadcrumbSeparatorProps extends ViewProps {
154+
className?: string;
155+
children?: React.ReactNode;
156+
mode?: 'light' | 'dark';
157+
}
158+
159+
const BreadcrumbSeparator = ({
160+
children,
161+
className,
162+
mode = 'light',
163+
...props
164+
}: BreadcrumbSeparatorProps) => {
165+
const iconColor = breadcrumbSeparatorClassNames.iconColor[mode];
166+
167+
return (
168+
<View
169+
accessible
170+
importantForAccessibility="no"
171+
className={cn(breadcrumbSeparatorClassNames.base, className)}
172+
{...props}
173+
>
174+
{children ?? <FeatherIcon name="chevron-right" size={14} color={iconColor} />}
175+
</View>
176+
);
177+
};
178+
179+
// BreadcrumbEllipsis (replaces span for ellipsis)
180+
interface BreadcrumbEllipsisProps extends ViewProps {
181+
className?: string;
182+
mode?: 'light' | 'dark';
183+
}
184+
185+
const BreadcrumbEllipsis = ({ className, mode = 'light', ...props }: BreadcrumbEllipsisProps) => {
186+
const iconColor = breadcrumbEllipsisClassNames.iconColor[mode];
187+
188+
return (
189+
<View
190+
accessible
191+
importantForAccessibility="no"
192+
accessibilityLabel="More breadcrumbs"
193+
className={cn(breadcrumbEllipsisClassNames.base, className)}
194+
{...props}
195+
>
196+
<FeatherIcon name="more-horizontal" size={16} color={iconColor} />
197+
<Text className="sr-only">More</Text>
198+
</View>
199+
);
200+
};
201+
202+
export {
203+
Breadcrumb,
204+
BreadcrumbList,
205+
BreadcrumbItem,
206+
BreadcrumbLink,
207+
BreadcrumbPage,
208+
BreadcrumbSeparator,
209+
BreadcrumbEllipsis,
210+
};
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
export const breadcrumbClassNames = {
2+
base: '',
3+
};
4+
5+
export const breadcrumbListClassNames = {
6+
base: 'flex-row flex-wrap items-center gap-1.5 sm:gap-2.5',
7+
light: 'text-muted-foreground',
8+
dark: 'text-dark-muted-foreground',
9+
};
10+
11+
export const breadcrumbItemClassNames = {
12+
base: 'flex-row items-center gap-1.5',
13+
};
14+
15+
export const breadcrumbLinkClassNames = {
16+
text: {
17+
base: 'text-sm',
18+
light: {
19+
default: 'text-muted-foreground',
20+
pressed: 'text-foreground',
21+
},
22+
dark: {
23+
default: 'text-dark-muted-foreground',
24+
pressed: 'text-dark-foreground',
25+
},
26+
},
27+
};
28+
29+
export const breadcrumbPageClassNames = {
30+
base: 'text-sm font-normal',
31+
light: 'text-foreground',
32+
dark: 'text-dark-foreground',
33+
};
34+
35+
export const breadcrumbSeparatorClassNames = {
36+
base: '',
37+
iconColor: {
38+
light: '#6B7280',
39+
dark: '#6B7280',
40+
},
41+
};
42+
43+
export const breadcrumbEllipsisClassNames = {
44+
base: 'items-center justify-center w-9 h-9',
45+
iconColor: {
46+
light: '#6B7280',
47+
dark: '#6B7280',
48+
},
49+
};

docs/pages/components/_meta.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
"alert-dialog": "Alert Dialog",
66
"avatar": "Avatar",
77
"skeleton": "Skeleton",
8-
"badge": "Badge"
8+
"badge": "Badge",
9+
"breadcrumb": "Breadcrumb"
910
}

0 commit comments

Comments
 (0)