@neoptocom/neopto-ui 1.4.2 → 1.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONSUMER_SETUP.md +1 -1
- package/README.md +2 -2
- package/dist/index.cjs +55 -0
- package/dist/index.d.cts +36 -1
- package/dist/index.d.ts +36 -1
- package/dist/index.js +55 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/components/Breadcrumb.tsx +110 -0
- package/src/index.ts +3 -1
- package/src/stories/Breadcrumb.stories.tsx +270 -0
- package/src/styles/library.css +1 -1
- package/src/styles/tailwind.css +1 -1
package/CONSUMER_SETUP.md
CHANGED
|
@@ -133,4 +133,4 @@ document.documentElement.classList.toggle("dark");
|
|
|
133
133
|
|
|
134
134
|
## 📚 Full Documentation
|
|
135
135
|
|
|
136
|
-
Visit the [Storybook documentation](https://neoptocom.github.io/neopto-ui
|
|
136
|
+
Visit the [Storybook documentation](https://neoptocom.github.io/neopto-ui) for interactive examples and API documentation.
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# NeoPTO UI
|
|
2
2
|
|
|
3
3
|
A modern React component library built with Tailwind CSS v4 and TypeScript. Features dark mode, design tokens, and comprehensive Storybook documentation.
|
|
4
4
|
|
|
@@ -195,7 +195,7 @@ The library uses a comprehensive design token system:
|
|
|
195
195
|
|
|
196
196
|
## 📚 Documentation
|
|
197
197
|
|
|
198
|
-
Visit our [Storybook documentation](https://neoptocom.github.io/neopto-ui/
|
|
198
|
+
Visit our [Storybook documentation](https://neoptocom.github.io/neopto-ui/) for:
|
|
199
199
|
|
|
200
200
|
- Interactive component playground
|
|
201
201
|
- Design system guidelines
|
package/dist/index.cjs
CHANGED
|
@@ -1391,6 +1391,60 @@ MessageBubble.displayName = "MessageBubble";
|
|
|
1391
1391
|
function Separator({ className = "" }) {
|
|
1392
1392
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `w-full my-1.5 h-px bg-[var(--border)] ${className}` });
|
|
1393
1393
|
}
|
|
1394
|
+
var Breadcrumb = ({
|
|
1395
|
+
items,
|
|
1396
|
+
showHomeIcon = false,
|
|
1397
|
+
className = ""
|
|
1398
|
+
}) => {
|
|
1399
|
+
if (!items || items.length === 0) {
|
|
1400
|
+
return null;
|
|
1401
|
+
}
|
|
1402
|
+
return /* @__PURE__ */ jsxRuntime.jsx("nav", { "aria-label": "Breadcrumb", className, children: /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "flex items-center flex-wrap", children: items.map((item, index) => {
|
|
1403
|
+
const isLast = index === items.length - 1;
|
|
1404
|
+
const isFirst = index === 0;
|
|
1405
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center", children: [
|
|
1406
|
+
item.href && !isLast ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1407
|
+
"a",
|
|
1408
|
+
{
|
|
1409
|
+
href: item.href,
|
|
1410
|
+
onClick: (e) => {
|
|
1411
|
+
if (item.onClick) {
|
|
1412
|
+
e.preventDefault();
|
|
1413
|
+
item.onClick();
|
|
1414
|
+
}
|
|
1415
|
+
},
|
|
1416
|
+
className: "group flex items-center gap-1 cursor-pointer text-[var(--muted-fg)]",
|
|
1417
|
+
children: [
|
|
1418
|
+
isFirst && showHomeIcon && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "home", size: "sm" }),
|
|
1419
|
+
item.icon && !showHomeIcon && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: item.icon, size: "sm" }),
|
|
1420
|
+
/* @__PURE__ */ jsxRuntime.jsx(Typo, { variant: "label-md", bold: "semibold", className: "group-hover:underline", children: item.label })
|
|
1421
|
+
]
|
|
1422
|
+
}
|
|
1423
|
+
) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1424
|
+
"span",
|
|
1425
|
+
{
|
|
1426
|
+
className: `group flex items-center gap-1 ${isLast ? "text-[var(--info)]" : "text-[var(--muted-fg)]"} ${item.onClick && !isLast ? "cursor-pointer" : ""}`,
|
|
1427
|
+
onClick: item.onClick,
|
|
1428
|
+
role: item.onClick && !isLast ? "button" : void 0,
|
|
1429
|
+
tabIndex: item.onClick && !isLast ? 0 : void 0,
|
|
1430
|
+
onKeyDown: item.onClick && !isLast ? (e) => {
|
|
1431
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1432
|
+
e.preventDefault();
|
|
1433
|
+
item.onClick?.();
|
|
1434
|
+
}
|
|
1435
|
+
} : void 0,
|
|
1436
|
+
"aria-current": isLast ? "page" : void 0,
|
|
1437
|
+
children: [
|
|
1438
|
+
isFirst && showHomeIcon && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "home", size: "sm" }),
|
|
1439
|
+
item.icon && !showHomeIcon && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: item.icon, size: "sm" }),
|
|
1440
|
+
/* @__PURE__ */ jsxRuntime.jsx(Typo, { variant: "label-md", bold: isLast ? "bold" : "semibold", className: item.onClick && !isLast ? "group-hover:underline" : "", children: item.label })
|
|
1441
|
+
]
|
|
1442
|
+
}
|
|
1443
|
+
),
|
|
1444
|
+
!isLast && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[var(--muted-fg)]", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "chevron_right", size: "md" }) })
|
|
1445
|
+
] }, index);
|
|
1446
|
+
}) }) });
|
|
1447
|
+
};
|
|
1394
1448
|
|
|
1395
1449
|
exports.AgentButton = AgentButton_default;
|
|
1396
1450
|
exports.AnimatedBgCircle = AnimatedBgCircle_default;
|
|
@@ -1400,6 +1454,7 @@ exports.Autocomplete = Autocomplete;
|
|
|
1400
1454
|
exports.Avatar = Avatar;
|
|
1401
1455
|
exports.AvatarGroup = AvatarGroup;
|
|
1402
1456
|
exports.BackgroundBlur = BackgroundBlur;
|
|
1457
|
+
exports.Breadcrumb = Breadcrumb;
|
|
1403
1458
|
exports.Button = Button;
|
|
1404
1459
|
exports.Card = Card;
|
|
1405
1460
|
exports.Chip = Chip;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
+
import React__default from 'react';
|
|
3
4
|
|
|
4
5
|
declare const bgLight: string;
|
|
5
6
|
declare const bgDark: string;
|
|
@@ -333,4 +334,38 @@ type SeparatorProps = {
|
|
|
333
334
|
};
|
|
334
335
|
declare function Separator({ className }: SeparatorProps): react_jsx_runtime.JSX.Element;
|
|
335
336
|
|
|
336
|
-
|
|
337
|
+
interface BreadcrumbItem {
|
|
338
|
+
/** Label to display */
|
|
339
|
+
label: string;
|
|
340
|
+
/** Optional href for navigation */
|
|
341
|
+
href?: string;
|
|
342
|
+
/** Optional icon name (Material Symbols) */
|
|
343
|
+
icon?: string;
|
|
344
|
+
/** Optional click handler */
|
|
345
|
+
onClick?: () => void;
|
|
346
|
+
}
|
|
347
|
+
interface BreadcrumbProps {
|
|
348
|
+
/** Array of breadcrumb items */
|
|
349
|
+
items: BreadcrumbItem[];
|
|
350
|
+
/** Whether to show home icon on first item */
|
|
351
|
+
showHomeIcon?: boolean;
|
|
352
|
+
/** Additional CSS classes */
|
|
353
|
+
className?: string;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Breadcrumb navigation component
|
|
357
|
+
*
|
|
358
|
+
* @example
|
|
359
|
+
* ```tsx
|
|
360
|
+
* <Breadcrumb
|
|
361
|
+
* items={[
|
|
362
|
+
* { label: "Home", href: "/" },
|
|
363
|
+
* { label: "Products", href: "/products" },
|
|
364
|
+
* { label: "Category" }
|
|
365
|
+
* ]}
|
|
366
|
+
* />
|
|
367
|
+
* ```
|
|
368
|
+
*/
|
|
369
|
+
declare const Breadcrumb: React__default.FC<BreadcrumbProps>;
|
|
370
|
+
|
|
371
|
+
export { AgentButton, type AgentButtonProps, AnimatedBgCircle, AnimatedBgRectangle, AppBackground, type AppBackgroundProps, Autocomplete, type AutocompleteOption, type AutocompleteProps, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, BackgroundBlur, type BackgroundBlurProps, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, Button, type ButtonProps, Card, type CardProps, Chip, type ChipProps, Counter, type CounterProps, Icon, IconButton, type IconButtonProps, type IconProps, Input, type InputProps, MessageBubble, type MessageBubbleProps, Modal, type ModalProps, Search, type SearchOption, type SearchProps, Separator, type SeparatorProps, Skeleton, type SkeletonProps, Textarea, type TextareaProps, Typo, type TypoProps, type TypoVariant, type TypoWeight, index as assets };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
+
import React__default from 'react';
|
|
3
4
|
|
|
4
5
|
declare const bgLight: string;
|
|
5
6
|
declare const bgDark: string;
|
|
@@ -333,4 +334,38 @@ type SeparatorProps = {
|
|
|
333
334
|
};
|
|
334
335
|
declare function Separator({ className }: SeparatorProps): react_jsx_runtime.JSX.Element;
|
|
335
336
|
|
|
336
|
-
|
|
337
|
+
interface BreadcrumbItem {
|
|
338
|
+
/** Label to display */
|
|
339
|
+
label: string;
|
|
340
|
+
/** Optional href for navigation */
|
|
341
|
+
href?: string;
|
|
342
|
+
/** Optional icon name (Material Symbols) */
|
|
343
|
+
icon?: string;
|
|
344
|
+
/** Optional click handler */
|
|
345
|
+
onClick?: () => void;
|
|
346
|
+
}
|
|
347
|
+
interface BreadcrumbProps {
|
|
348
|
+
/** Array of breadcrumb items */
|
|
349
|
+
items: BreadcrumbItem[];
|
|
350
|
+
/** Whether to show home icon on first item */
|
|
351
|
+
showHomeIcon?: boolean;
|
|
352
|
+
/** Additional CSS classes */
|
|
353
|
+
className?: string;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Breadcrumb navigation component
|
|
357
|
+
*
|
|
358
|
+
* @example
|
|
359
|
+
* ```tsx
|
|
360
|
+
* <Breadcrumb
|
|
361
|
+
* items={[
|
|
362
|
+
* { label: "Home", href: "/" },
|
|
363
|
+
* { label: "Products", href: "/products" },
|
|
364
|
+
* { label: "Category" }
|
|
365
|
+
* ]}
|
|
366
|
+
* />
|
|
367
|
+
* ```
|
|
368
|
+
*/
|
|
369
|
+
declare const Breadcrumb: React__default.FC<BreadcrumbProps>;
|
|
370
|
+
|
|
371
|
+
export { AgentButton, type AgentButtonProps, AnimatedBgCircle, AnimatedBgRectangle, AppBackground, type AppBackgroundProps, Autocomplete, type AutocompleteOption, type AutocompleteProps, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, BackgroundBlur, type BackgroundBlurProps, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, Button, type ButtonProps, Card, type CardProps, Chip, type ChipProps, Counter, type CounterProps, Icon, IconButton, type IconButtonProps, type IconProps, Input, type InputProps, MessageBubble, type MessageBubbleProps, Modal, type ModalProps, Search, type SearchOption, type SearchProps, Separator, type SeparatorProps, Skeleton, type SkeletonProps, Textarea, type TextareaProps, Typo, type TypoProps, type TypoVariant, type TypoWeight, index as assets };
|
package/dist/index.js
CHANGED
|
@@ -1370,5 +1370,59 @@ MessageBubble.displayName = "MessageBubble";
|
|
|
1370
1370
|
function Separator({ className = "" }) {
|
|
1371
1371
|
return /* @__PURE__ */ jsx("div", { className: `w-full my-1.5 h-px bg-[var(--border)] ${className}` });
|
|
1372
1372
|
}
|
|
1373
|
+
var Breadcrumb = ({
|
|
1374
|
+
items,
|
|
1375
|
+
showHomeIcon = false,
|
|
1376
|
+
className = ""
|
|
1377
|
+
}) => {
|
|
1378
|
+
if (!items || items.length === 0) {
|
|
1379
|
+
return null;
|
|
1380
|
+
}
|
|
1381
|
+
return /* @__PURE__ */ jsx("nav", { "aria-label": "Breadcrumb", className, children: /* @__PURE__ */ jsx("ol", { className: "flex items-center flex-wrap", children: items.map((item, index) => {
|
|
1382
|
+
const isLast = index === items.length - 1;
|
|
1383
|
+
const isFirst = index === 0;
|
|
1384
|
+
return /* @__PURE__ */ jsxs("li", { className: "flex items-center", children: [
|
|
1385
|
+
item.href && !isLast ? /* @__PURE__ */ jsxs(
|
|
1386
|
+
"a",
|
|
1387
|
+
{
|
|
1388
|
+
href: item.href,
|
|
1389
|
+
onClick: (e) => {
|
|
1390
|
+
if (item.onClick) {
|
|
1391
|
+
e.preventDefault();
|
|
1392
|
+
item.onClick();
|
|
1393
|
+
}
|
|
1394
|
+
},
|
|
1395
|
+
className: "group flex items-center gap-1 cursor-pointer text-[var(--muted-fg)]",
|
|
1396
|
+
children: [
|
|
1397
|
+
isFirst && showHomeIcon && /* @__PURE__ */ jsx(Icon, { name: "home", size: "sm" }),
|
|
1398
|
+
item.icon && !showHomeIcon && /* @__PURE__ */ jsx(Icon, { name: item.icon, size: "sm" }),
|
|
1399
|
+
/* @__PURE__ */ jsx(Typo, { variant: "label-md", bold: "semibold", className: "group-hover:underline", children: item.label })
|
|
1400
|
+
]
|
|
1401
|
+
}
|
|
1402
|
+
) : /* @__PURE__ */ jsxs(
|
|
1403
|
+
"span",
|
|
1404
|
+
{
|
|
1405
|
+
className: `group flex items-center gap-1 ${isLast ? "text-[var(--info)]" : "text-[var(--muted-fg)]"} ${item.onClick && !isLast ? "cursor-pointer" : ""}`,
|
|
1406
|
+
onClick: item.onClick,
|
|
1407
|
+
role: item.onClick && !isLast ? "button" : void 0,
|
|
1408
|
+
tabIndex: item.onClick && !isLast ? 0 : void 0,
|
|
1409
|
+
onKeyDown: item.onClick && !isLast ? (e) => {
|
|
1410
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1411
|
+
e.preventDefault();
|
|
1412
|
+
item.onClick?.();
|
|
1413
|
+
}
|
|
1414
|
+
} : void 0,
|
|
1415
|
+
"aria-current": isLast ? "page" : void 0,
|
|
1416
|
+
children: [
|
|
1417
|
+
isFirst && showHomeIcon && /* @__PURE__ */ jsx(Icon, { name: "home", size: "sm" }),
|
|
1418
|
+
item.icon && !showHomeIcon && /* @__PURE__ */ jsx(Icon, { name: item.icon, size: "sm" }),
|
|
1419
|
+
/* @__PURE__ */ jsx(Typo, { variant: "label-md", bold: isLast ? "bold" : "semibold", className: item.onClick && !isLast ? "group-hover:underline" : "", children: item.label })
|
|
1420
|
+
]
|
|
1421
|
+
}
|
|
1422
|
+
),
|
|
1423
|
+
!isLast && /* @__PURE__ */ jsx("span", { className: "text-[var(--muted-fg)]", children: /* @__PURE__ */ jsx(Icon, { name: "chevron_right", size: "md" }) })
|
|
1424
|
+
] }, index);
|
|
1425
|
+
}) }) });
|
|
1426
|
+
};
|
|
1373
1427
|
|
|
1374
|
-
export { AgentButton_default as AgentButton, AnimatedBgCircle_default as AnimatedBgCircle, AnimatedBgRectangle_default as AnimatedBgRectangle, AppBackground, Autocomplete, Avatar, AvatarGroup, BackgroundBlur, Button, Card, Chip, Counter, Icon, IconButton, Input, MessageBubble, Modal, Search, Separator, Skeleton, Textarea, Typo, assets_exports as assets };
|
|
1428
|
+
export { AgentButton_default as AgentButton, AnimatedBgCircle_default as AnimatedBgCircle, AnimatedBgRectangle_default as AnimatedBgRectangle, AppBackground, Autocomplete, Avatar, AvatarGroup, BackgroundBlur, Breadcrumb, Button, Card, Chip, Counter, Icon, IconButton, Input, MessageBubble, Modal, Search, Separator, Skeleton, Textarea, Typo, assets_exports as assets };
|
package/dist/styles.css
CHANGED
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
font-style: normal;
|
|
94
94
|
font-weight: 100 700;
|
|
95
95
|
src: url(https://fonts.gstatic.com/s/materialsymbolsrounded/v272/sykg-zNym6YjUruM-QrEh7-nyTnjDwKNJ_190FjzaqkNCeE.woff2) format('woff2');
|
|
96
|
-
font-display:
|
|
96
|
+
font-display: block;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
.material-symbols-rounded {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neoptocom/neopto-ui",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A modern React component library built with Tailwind CSS v4 and TypeScript. Features dark mode, design tokens, and comprehensive Storybook documentation. Requires Tailwind v4+.",
|
|
6
6
|
"keywords": [
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import Icon from "./Icon";
|
|
3
|
+
import Typo from "./Typo";
|
|
4
|
+
|
|
5
|
+
export interface BreadcrumbItem {
|
|
6
|
+
/** Label to display */
|
|
7
|
+
label: string;
|
|
8
|
+
/** Optional href for navigation */
|
|
9
|
+
href?: string;
|
|
10
|
+
/** Optional icon name (Material Symbols) */
|
|
11
|
+
icon?: string;
|
|
12
|
+
/** Optional click handler */
|
|
13
|
+
onClick?: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface BreadcrumbProps {
|
|
17
|
+
/** Array of breadcrumb items */
|
|
18
|
+
items: BreadcrumbItem[];
|
|
19
|
+
/** Whether to show home icon on first item */
|
|
20
|
+
showHomeIcon?: boolean;
|
|
21
|
+
/** Additional CSS classes */
|
|
22
|
+
className?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Breadcrumb navigation component
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* <Breadcrumb
|
|
31
|
+
* items={[
|
|
32
|
+
* { label: "Home", href: "/" },
|
|
33
|
+
* { label: "Products", href: "/products" },
|
|
34
|
+
* { label: "Category" }
|
|
35
|
+
* ]}
|
|
36
|
+
* />
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export const Breadcrumb: React.FC<BreadcrumbProps> = ({
|
|
40
|
+
items,
|
|
41
|
+
showHomeIcon = false,
|
|
42
|
+
className = "",
|
|
43
|
+
}) => {
|
|
44
|
+
if (!items || items.length === 0) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<nav aria-label="Breadcrumb" className={className}>
|
|
50
|
+
<ol className="flex items-center flex-wrap">
|
|
51
|
+
{items.map((item, index) => {
|
|
52
|
+
const isLast = index === items.length - 1;
|
|
53
|
+
const isFirst = index === 0;
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<li key={index} className="flex items-center">
|
|
57
|
+
{/* Breadcrumb Item */}
|
|
58
|
+
{item.href && !isLast ? (
|
|
59
|
+
<a
|
|
60
|
+
href={item.href}
|
|
61
|
+
onClick={(e) => {
|
|
62
|
+
if (item.onClick) {
|
|
63
|
+
e.preventDefault();
|
|
64
|
+
item.onClick();
|
|
65
|
+
}
|
|
66
|
+
}}
|
|
67
|
+
className="group flex items-center gap-1 cursor-pointer text-[var(--muted-fg)]"
|
|
68
|
+
>
|
|
69
|
+
{isFirst && showHomeIcon && <Icon name="home" size="sm" />}
|
|
70
|
+
{item.icon && !showHomeIcon && <Icon name={item.icon} size="sm" />}
|
|
71
|
+
<Typo variant="label-md" bold="semibold" className="group-hover:underline">{item.label}</Typo>
|
|
72
|
+
</a>
|
|
73
|
+
) : (
|
|
74
|
+
<span
|
|
75
|
+
className={`group flex items-center gap-1 ${isLast ? "text-[var(--info)]" : "text-[var(--muted-fg)]"} ${item.onClick && !isLast ? "cursor-pointer" : ""}`}
|
|
76
|
+
onClick={item.onClick}
|
|
77
|
+
role={item.onClick && !isLast ? "button" : undefined}
|
|
78
|
+
tabIndex={item.onClick && !isLast ? 0 : undefined}
|
|
79
|
+
onKeyDown={
|
|
80
|
+
item.onClick && !isLast
|
|
81
|
+
? (e) => {
|
|
82
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
83
|
+
e.preventDefault();
|
|
84
|
+
item.onClick?.();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
: undefined
|
|
88
|
+
}
|
|
89
|
+
aria-current={isLast ? "page" : undefined}
|
|
90
|
+
>
|
|
91
|
+
{isFirst && showHomeIcon && <Icon name="home" size="sm" />}
|
|
92
|
+
{item.icon && !showHomeIcon && <Icon name={item.icon} size="sm" />}
|
|
93
|
+
<Typo variant="label-md" bold={isLast ? "bold" : "semibold"} className={item.onClick && !isLast ? "group-hover:underline" : ""}>{item.label}</Typo>
|
|
94
|
+
</span>
|
|
95
|
+
)}
|
|
96
|
+
|
|
97
|
+
{/* Separator */}
|
|
98
|
+
{!isLast && (
|
|
99
|
+
<span className="text-[var(--muted-fg)]">
|
|
100
|
+
<Icon name="chevron_right" size="md" />
|
|
101
|
+
</span>
|
|
102
|
+
)}
|
|
103
|
+
</li>
|
|
104
|
+
);
|
|
105
|
+
})}
|
|
106
|
+
</ol>
|
|
107
|
+
</nav>
|
|
108
|
+
);
|
|
109
|
+
};
|
|
110
|
+
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ export { default as Counter } from "./components/Counter";
|
|
|
22
22
|
export * from "./components/Chat";
|
|
23
23
|
export * from "./components/MessageBubble";
|
|
24
24
|
export * from "./components/Separator";
|
|
25
|
+
export { Breadcrumb } from "./components/Breadcrumb";
|
|
25
26
|
|
|
26
27
|
// Types
|
|
27
28
|
export type { AppBackgroundProps } from "./components/AppBackground";
|
|
@@ -43,4 +44,5 @@ export type { ChipProps } from "./components/Chip";
|
|
|
43
44
|
export type { CounterProps } from "./components/Counter";
|
|
44
45
|
export type { AgentButtonProps } from "./components/Chat";
|
|
45
46
|
export type { MessageBubbleProps } from "./components/MessageBubble";
|
|
46
|
-
export type { SeparatorProps } from "./components/Separator";
|
|
47
|
+
export type { SeparatorProps } from "./components/Separator";
|
|
48
|
+
export type { BreadcrumbProps, BreadcrumbItem } from "./components/Breadcrumb";
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Breadcrumb } from "../components/Breadcrumb";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Breadcrumb> = {
|
|
6
|
+
title: "Components/Breadcrumb",
|
|
7
|
+
component: Breadcrumb,
|
|
8
|
+
parameters: {
|
|
9
|
+
layout: "centered",
|
|
10
|
+
},
|
|
11
|
+
tags: ["autodocs"],
|
|
12
|
+
argTypes: {
|
|
13
|
+
items: {
|
|
14
|
+
description: "Array of breadcrumb items to display",
|
|
15
|
+
control: { type: "object" },
|
|
16
|
+
},
|
|
17
|
+
showHomeIcon: {
|
|
18
|
+
description: "Show home icon on first item",
|
|
19
|
+
control: { type: "boolean" },
|
|
20
|
+
},
|
|
21
|
+
className: {
|
|
22
|
+
description: "Additional CSS classes",
|
|
23
|
+
control: { type: "text" },
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default meta;
|
|
29
|
+
type Story = StoryObj<typeof Breadcrumb>;
|
|
30
|
+
|
|
31
|
+
// Basic Examples
|
|
32
|
+
export const Default: Story = {
|
|
33
|
+
args: {
|
|
34
|
+
items: [
|
|
35
|
+
{ label: "Home", href: "/" },
|
|
36
|
+
{ label: "Products", href: "/products" },
|
|
37
|
+
{ label: "Electronics", href: "/products/electronics" },
|
|
38
|
+
{ label: "Laptops" },
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const WithHomeIcon: Story = {
|
|
44
|
+
args: {
|
|
45
|
+
items: [
|
|
46
|
+
{ label: "Home", href: "/" },
|
|
47
|
+
{ label: "Products", href: "/products" },
|
|
48
|
+
{ label: "Electronics" },
|
|
49
|
+
],
|
|
50
|
+
showHomeIcon: true,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const TwoLevels: Story = {
|
|
55
|
+
args: {
|
|
56
|
+
items: [
|
|
57
|
+
{ label: "Home", href: "/" },
|
|
58
|
+
{ label: "Current Page" },
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const SingleItem: Story = {
|
|
64
|
+
args: {
|
|
65
|
+
items: [{ label: "Home" }],
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
// With Icons
|
|
71
|
+
export const WithIcons: Story = {
|
|
72
|
+
args: {
|
|
73
|
+
items: [
|
|
74
|
+
{ label: "Home", href: "/", icon: "home" },
|
|
75
|
+
{ label: "Settings", href: "/settings", icon: "settings" },
|
|
76
|
+
{ label: "Profile", icon: "person" },
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const MixedIcons: Story = {
|
|
82
|
+
args: {
|
|
83
|
+
items: [
|
|
84
|
+
{ label: "Dashboard", href: "/", icon: "dashboard" },
|
|
85
|
+
{ label: "Projects", href: "/projects", icon: "folder" },
|
|
86
|
+
{ label: "Documents", href: "/documents", icon: "description" },
|
|
87
|
+
{ label: "Report.pdf" },
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// With Click Handlers
|
|
93
|
+
export const WithClickHandlers: Story = {
|
|
94
|
+
args: {
|
|
95
|
+
items: [
|
|
96
|
+
{
|
|
97
|
+
label: "Home",
|
|
98
|
+
onClick: () => alert("Navigating to Home"),
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
label: "Products",
|
|
102
|
+
onClick: () => alert("Navigating to Products"),
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
label: "Category",
|
|
106
|
+
onClick: () => alert("Navigating to Category"),
|
|
107
|
+
},
|
|
108
|
+
{ label: "Item" },
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// Interactive Component Navigation (no URL changes)
|
|
114
|
+
export const ComponentNavigation = () => {
|
|
115
|
+
const sections = [
|
|
116
|
+
{ id: "account", label: "Account", icon: "person" },
|
|
117
|
+
{ id: "security", label: "Security", icon: "lock" },
|
|
118
|
+
{ id: "notifications", label: "Notifications", icon: "notifications" },
|
|
119
|
+
{ id: "billing", label: "Billing", icon: "payment" },
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
const [activeSection, setActiveSection] = useState("account");
|
|
123
|
+
|
|
124
|
+
const sectionContent: Record<string, { title: string; description: string }> = {
|
|
125
|
+
account: {
|
|
126
|
+
title: "Account Settings",
|
|
127
|
+
description: "Manage your account information, profile details, and preferences.",
|
|
128
|
+
},
|
|
129
|
+
security: {
|
|
130
|
+
title: "Security Settings",
|
|
131
|
+
description: "Configure password, two-factor authentication, and security preferences.",
|
|
132
|
+
},
|
|
133
|
+
notifications: {
|
|
134
|
+
title: "Notification Preferences",
|
|
135
|
+
description: "Control how and when you receive notifications from our platform.",
|
|
136
|
+
},
|
|
137
|
+
billing: {
|
|
138
|
+
title: "Billing & Subscription",
|
|
139
|
+
description: "View invoices, update payment methods, and manage your subscription.",
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const breadcrumbItems = [
|
|
144
|
+
{
|
|
145
|
+
label: "Settings",
|
|
146
|
+
icon: "settings",
|
|
147
|
+
onClick: () => setActiveSection("account"),
|
|
148
|
+
},
|
|
149
|
+
...sections
|
|
150
|
+
.slice(0, sections.findIndex((s) => s.id === activeSection) + 1)
|
|
151
|
+
.map((section, index, arr) => ({
|
|
152
|
+
label: section.label,
|
|
153
|
+
icon: section.icon,
|
|
154
|
+
onClick: index < arr.length - 1 ? () => setActiveSection(section.id) : undefined,
|
|
155
|
+
})),
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<div className="w-full max-w-4xl p-6 space-y-6">
|
|
160
|
+
{/* Breadcrumb Navigation */}
|
|
161
|
+
<Breadcrumb items={breadcrumbItems} />
|
|
162
|
+
|
|
163
|
+
{/* Content Area */}
|
|
164
|
+
<div className="border rounded-lg p-6 space-y-4">
|
|
165
|
+
<h2 className="text-2xl font-semibold">{sectionContent[activeSection].title}</h2>
|
|
166
|
+
<p className="text-gray-600">{sectionContent[activeSection].description}</p>
|
|
167
|
+
|
|
168
|
+
{/* Navigation Buttons */}
|
|
169
|
+
<div className="flex gap-2 pt-4">
|
|
170
|
+
{sections.map((section) => (
|
|
171
|
+
<button
|
|
172
|
+
key={section.id}
|
|
173
|
+
onClick={() => setActiveSection(section.id)}
|
|
174
|
+
className={`px-4 py-2 rounded transition ${
|
|
175
|
+
activeSection === section.id
|
|
176
|
+
? "bg-blue-500 text-white"
|
|
177
|
+
: "bg-gray-100 hover:bg-gray-200"
|
|
178
|
+
}`}
|
|
179
|
+
>
|
|
180
|
+
{section.label}
|
|
181
|
+
</button>
|
|
182
|
+
))}
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
{/* Usage Example Code */}
|
|
187
|
+
<div className="bg-gray-50 p-4 rounded text-sm">
|
|
188
|
+
<p className="font-mono text-xs text-gray-600">
|
|
189
|
+
Current section: <strong>{activeSection}</strong>
|
|
190
|
+
</p>
|
|
191
|
+
<p className="text-xs text-gray-500 mt-2">
|
|
192
|
+
Click any breadcrumb item or button to navigate between sections without changing the URL.
|
|
193
|
+
</p>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// Long Breadcrumb
|
|
200
|
+
export const LongBreadcrumb: Story = {
|
|
201
|
+
args: {
|
|
202
|
+
items: [
|
|
203
|
+
{ label: "Home", href: "/" },
|
|
204
|
+
{ label: "Products", href: "/products" },
|
|
205
|
+
{ label: "Electronics", href: "/products/electronics" },
|
|
206
|
+
{ label: "Computers", href: "/products/electronics/computers" },
|
|
207
|
+
{ label: "Laptops", href: "/products/electronics/computers/laptops" },
|
|
208
|
+
{ label: "Gaming Laptops", href: "/products/electronics/computers/laptops/gaming" },
|
|
209
|
+
{ label: "ASUS ROG" },
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// Navigation Patterns
|
|
215
|
+
export const FileSystem: Story = {
|
|
216
|
+
args: {
|
|
217
|
+
items: [
|
|
218
|
+
{ label: "Root", href: "/", icon: "folder" },
|
|
219
|
+
{ label: "Documents", href: "/documents", icon: "folder" },
|
|
220
|
+
{ label: "Projects", href: "/documents/projects", icon: "folder" },
|
|
221
|
+
{ label: "2024", href: "/documents/projects/2024", icon: "folder" },
|
|
222
|
+
{ label: "report.pdf", icon: "description" },
|
|
223
|
+
],
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
export const AdminPanel: Story = {
|
|
228
|
+
args: {
|
|
229
|
+
items: [
|
|
230
|
+
{ label: "Dashboard", href: "/", icon: "dashboard" },
|
|
231
|
+
{ label: "Users", href: "/users", icon: "group" },
|
|
232
|
+
{ label: "Edit User", icon: "edit" },
|
|
233
|
+
],
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export const ECommerce: Story = {
|
|
238
|
+
args: {
|
|
239
|
+
items: [
|
|
240
|
+
{ label: "Home", href: "/" },
|
|
241
|
+
{ label: "Electronics", href: "/electronics" },
|
|
242
|
+
{ label: "Smartphones", href: "/electronics/smartphones" },
|
|
243
|
+
{ label: "iPhone 15 Pro" },
|
|
244
|
+
],
|
|
245
|
+
showHomeIcon: true,
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// Edge Cases
|
|
250
|
+
export const VeryLongLabels: Story = {
|
|
251
|
+
args: {
|
|
252
|
+
items: [
|
|
253
|
+
{ label: "Home", href: "/" },
|
|
254
|
+
{ label: "This is a very long breadcrumb label that might wrap", href: "/long" },
|
|
255
|
+
{ label: "Another extremely long label for testing purposes" },
|
|
256
|
+
],
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
export const WithCustomClassName: Story = {
|
|
261
|
+
args: {
|
|
262
|
+
items: [
|
|
263
|
+
{ label: "Home", href: "/" },
|
|
264
|
+
{ label: "Products", href: "/products" },
|
|
265
|
+
{ label: "Category" },
|
|
266
|
+
],
|
|
267
|
+
className: "text-blue-600",
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
|
package/src/styles/library.css
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
font-style: normal;
|
|
17
17
|
font-weight: 100 700;
|
|
18
18
|
src: url(https://fonts.gstatic.com/s/materialsymbolsrounded/v272/sykg-zNym6YjUruM-QrEh7-nyTnjDwKNJ_190FjzaqkNCeE.woff2) format('woff2');
|
|
19
|
-
font-display:
|
|
19
|
+
font-display: block;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
.material-symbols-rounded {
|
package/src/styles/tailwind.css
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
font-style: normal;
|
|
20
20
|
font-weight: 100 700;
|
|
21
21
|
src: url(https://fonts.gstatic.com/s/materialsymbolsrounded/v272/sykg-zNym6YjUruM-QrEh7-nyTnjDwKNJ_190FjzaqkNCeE.woff2) format('woff2');
|
|
22
|
-
font-display:
|
|
22
|
+
font-display: block;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
.material-symbols-rounded {
|