@neoptocom/neopto-ui 1.4.3 → 1.5.0
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/dist/index.cjs +129 -21
- package/dist/index.d.cts +53 -2
- package/dist/index.d.ts +53 -2
- package/dist/index.js +129 -22
- package/package.json +3 -2
- package/src/components/Breadcrumb.docs.mdx +60 -0
- package/src/components/Breadcrumb.stories.tsx +78 -0
- package/src/components/Breadcrumb.tsx +110 -0
- package/src/components/Button.docs.mdx +56 -0
- package/src/{stories → components}/Button.stories.tsx +39 -32
- package/src/components/Card.docs.mdx +56 -0
- package/src/components/Card.stories.tsx +129 -0
- package/src/components/Input.tsx +110 -25
- package/src/index.ts +3 -1
- package/src/stories/Input.stories.tsx +26 -0
- package/src/stories/Card.stories.tsx +0 -350
package/dist/index.cjs
CHANGED
|
@@ -265,28 +265,81 @@ function Card({
|
|
|
265
265
|
);
|
|
266
266
|
}
|
|
267
267
|
var Input = React3__namespace.forwardRef(
|
|
268
|
-
({
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
268
|
+
({
|
|
269
|
+
className,
|
|
270
|
+
disabled,
|
|
271
|
+
variant = "default",
|
|
272
|
+
label,
|
|
273
|
+
fieldsetProps,
|
|
274
|
+
legendProps,
|
|
275
|
+
error = false,
|
|
276
|
+
...props
|
|
277
|
+
}, ref) => {
|
|
278
|
+
const isInlineVariant = variant === "inline";
|
|
279
|
+
const shouldUseInlineStyles = isInlineVariant || Boolean(label);
|
|
280
|
+
const isError = error && !disabled;
|
|
281
|
+
const inputClasses = [
|
|
282
|
+
"w-full bg-transparent outline-none transition-colors",
|
|
283
|
+
shouldUseInlineStyles ? "h-9" : "h-12 px-4 rounded-full",
|
|
284
|
+
"font-['Poppins'] text-sm placeholder:text-[var(--muted-fg)]"
|
|
285
|
+
];
|
|
286
|
+
if (!shouldUseInlineStyles) {
|
|
287
|
+
inputClasses.push("border");
|
|
288
|
+
}
|
|
289
|
+
if (disabled) {
|
|
290
|
+
inputClasses.push("text-[#3F424F]", "cursor-not-allowed");
|
|
291
|
+
if (!shouldUseInlineStyles) {
|
|
292
|
+
inputClasses.push("border-[#3F424F]");
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
inputClasses.push("text-[var(--muted-fg)]", "focus:text-[var(--fg)]");
|
|
296
|
+
if (!shouldUseInlineStyles) {
|
|
297
|
+
inputClasses.push(
|
|
298
|
+
isError ? "border-[var(--destructive)]" : "border-[var(--muted-fg)]",
|
|
299
|
+
isError ? "hover:border-[var(--destructive)]" : "hover:border-[var(--border)]",
|
|
300
|
+
isError ? "focus:border-[var(--destructive)]" : "focus:border-[var(--color-brand)]"
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (className) {
|
|
305
|
+
inputClasses.push(className);
|
|
306
|
+
}
|
|
307
|
+
const inputClassName = inputClasses.join(" ");
|
|
308
|
+
const inputElement = /* @__PURE__ */ jsxRuntime.jsx("input", { ref, disabled, className: inputClassName, ...props });
|
|
309
|
+
if (!label) {
|
|
310
|
+
return inputElement;
|
|
311
|
+
}
|
|
312
|
+
const { className: fieldsetClassNameProp = "", ...restFieldsetProps } = fieldsetProps ?? {};
|
|
313
|
+
const { className: legendClassNameProp = "", ...restLegendProps } = legendProps ?? {};
|
|
314
|
+
const fieldsetClassName = [
|
|
315
|
+
"w-full min-w-0 rounded-full border bg-[var(--surface)] transition-colors h-14",
|
|
316
|
+
isError ? "border-[var(--destructive)]" : "border-[var(--border)]",
|
|
317
|
+
isError ? "focus-within:border-[var(--destructive)]" : "focus-within:border-[var(--color-brand)]",
|
|
318
|
+
disabled ? "opacity-60 cursor-not-allowed" : "",
|
|
319
|
+
fieldsetClassNameProp
|
|
320
|
+
].filter(Boolean).join(" ");
|
|
321
|
+
const legendColorClass = disabled ? "text-[var(--muted-fg)]" : isError ? "text-[var(--destructive)]" : "text-[var(--muted-fg)]";
|
|
322
|
+
const legendClassNameCombined = [
|
|
323
|
+
"ml-4 px-1 text-sm leading-none relative font-normal select-none",
|
|
324
|
+
legendColorClass,
|
|
325
|
+
legendClassNameProp
|
|
326
|
+
].filter(Boolean).join(" ");
|
|
327
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
328
|
+
"fieldset",
|
|
272
329
|
{
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
].join(" "),
|
|
287
|
-
className
|
|
288
|
-
].join(" "),
|
|
289
|
-
...props
|
|
330
|
+
...restFieldsetProps,
|
|
331
|
+
className: fieldsetClassName,
|
|
332
|
+
children: [
|
|
333
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
334
|
+
"legend",
|
|
335
|
+
{
|
|
336
|
+
...restLegendProps,
|
|
337
|
+
className: legendClassNameCombined,
|
|
338
|
+
children: label
|
|
339
|
+
}
|
|
340
|
+
),
|
|
341
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative flex pl-5 pr-3 pb-1 h-full", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex w-full", children: inputElement }) })
|
|
342
|
+
]
|
|
290
343
|
}
|
|
291
344
|
);
|
|
292
345
|
}
|
|
@@ -1391,6 +1444,60 @@ MessageBubble.displayName = "MessageBubble";
|
|
|
1391
1444
|
function Separator({ className = "" }) {
|
|
1392
1445
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `w-full my-1.5 h-px bg-[var(--border)] ${className}` });
|
|
1393
1446
|
}
|
|
1447
|
+
var Breadcrumb = ({
|
|
1448
|
+
items,
|
|
1449
|
+
showHomeIcon = false,
|
|
1450
|
+
className = ""
|
|
1451
|
+
}) => {
|
|
1452
|
+
if (!items || items.length === 0) {
|
|
1453
|
+
return null;
|
|
1454
|
+
}
|
|
1455
|
+
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) => {
|
|
1456
|
+
const isLast = index === items.length - 1;
|
|
1457
|
+
const isFirst = index === 0;
|
|
1458
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center", children: [
|
|
1459
|
+
item.href && !isLast ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1460
|
+
"a",
|
|
1461
|
+
{
|
|
1462
|
+
href: item.href,
|
|
1463
|
+
onClick: (e) => {
|
|
1464
|
+
if (item.onClick) {
|
|
1465
|
+
e.preventDefault();
|
|
1466
|
+
item.onClick();
|
|
1467
|
+
}
|
|
1468
|
+
},
|
|
1469
|
+
className: "group flex items-center gap-1 cursor-pointer text-[var(--muted-fg)]",
|
|
1470
|
+
children: [
|
|
1471
|
+
isFirst && showHomeIcon && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "home", size: "sm" }),
|
|
1472
|
+
item.icon && !showHomeIcon && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: item.icon, size: "sm" }),
|
|
1473
|
+
/* @__PURE__ */ jsxRuntime.jsx(Typo, { variant: "label-md", bold: "semibold", className: "group-hover:underline", children: item.label })
|
|
1474
|
+
]
|
|
1475
|
+
}
|
|
1476
|
+
) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1477
|
+
"span",
|
|
1478
|
+
{
|
|
1479
|
+
className: `group flex items-center gap-1 ${isLast ? "text-[var(--info)]" : "text-[var(--muted-fg)]"} ${item.onClick && !isLast ? "cursor-pointer" : ""}`,
|
|
1480
|
+
onClick: item.onClick,
|
|
1481
|
+
role: item.onClick && !isLast ? "button" : void 0,
|
|
1482
|
+
tabIndex: item.onClick && !isLast ? 0 : void 0,
|
|
1483
|
+
onKeyDown: item.onClick && !isLast ? (e) => {
|
|
1484
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1485
|
+
e.preventDefault();
|
|
1486
|
+
item.onClick?.();
|
|
1487
|
+
}
|
|
1488
|
+
} : void 0,
|
|
1489
|
+
"aria-current": isLast ? "page" : void 0,
|
|
1490
|
+
children: [
|
|
1491
|
+
isFirst && showHomeIcon && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "home", size: "sm" }),
|
|
1492
|
+
item.icon && !showHomeIcon && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: item.icon, size: "sm" }),
|
|
1493
|
+
/* @__PURE__ */ jsxRuntime.jsx(Typo, { variant: "label-md", bold: isLast ? "bold" : "semibold", className: item.onClick && !isLast ? "group-hover:underline" : "", children: item.label })
|
|
1494
|
+
]
|
|
1495
|
+
}
|
|
1496
|
+
),
|
|
1497
|
+
!isLast && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[var(--muted-fg)]", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "chevron_right", size: "md" }) })
|
|
1498
|
+
] }, index);
|
|
1499
|
+
}) }) });
|
|
1500
|
+
};
|
|
1394
1501
|
|
|
1395
1502
|
exports.AgentButton = AgentButton_default;
|
|
1396
1503
|
exports.AnimatedBgCircle = AnimatedBgCircle_default;
|
|
@@ -1400,6 +1507,7 @@ exports.Autocomplete = Autocomplete;
|
|
|
1400
1507
|
exports.Avatar = Avatar;
|
|
1401
1508
|
exports.AvatarGroup = AvatarGroup;
|
|
1402
1509
|
exports.BackgroundBlur = BackgroundBlur;
|
|
1510
|
+
exports.Breadcrumb = Breadcrumb;
|
|
1403
1511
|
exports.Button = Button;
|
|
1404
1512
|
exports.Card = Card;
|
|
1405
1513
|
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;
|
|
@@ -58,13 +59,29 @@ type CardProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
|
58
59
|
};
|
|
59
60
|
declare function Card({ children, className, style, showDecorations, variant, elevated, lightImage, darkImage, ...props }: CardProps): react_jsx_runtime.JSX.Element;
|
|
60
61
|
|
|
61
|
-
type InputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>,
|
|
62
|
+
type InputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> & {
|
|
62
63
|
/** Input visual variant */
|
|
63
64
|
variant?: "default" | "inline";
|
|
65
|
+
/** Optional floating label (renders a fieldset wrapper when provided) */
|
|
66
|
+
label?: string;
|
|
67
|
+
/** Additional props for the surrounding fieldset when label is set */
|
|
68
|
+
fieldsetProps?: React.FieldsetHTMLAttributes<HTMLFieldSetElement>;
|
|
69
|
+
/** Additional props for the legend when label is set */
|
|
70
|
+
legendProps?: React.HTMLAttributes<HTMLLegendElement>;
|
|
71
|
+
/** Flag to visually mark the input as errored */
|
|
72
|
+
error?: boolean;
|
|
64
73
|
};
|
|
65
74
|
declare const Input: React.ForwardRefExoticComponent<Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> & {
|
|
66
75
|
/** Input visual variant */
|
|
67
76
|
variant?: "default" | "inline";
|
|
77
|
+
/** Optional floating label (renders a fieldset wrapper when provided) */
|
|
78
|
+
label?: string;
|
|
79
|
+
/** Additional props for the surrounding fieldset when label is set */
|
|
80
|
+
fieldsetProps?: React.FieldsetHTMLAttributes<HTMLFieldSetElement>;
|
|
81
|
+
/** Additional props for the legend when label is set */
|
|
82
|
+
legendProps?: React.HTMLAttributes<HTMLLegendElement>;
|
|
83
|
+
/** Flag to visually mark the input as errored */
|
|
84
|
+
error?: boolean;
|
|
68
85
|
} & React.RefAttributes<HTMLInputElement>>;
|
|
69
86
|
|
|
70
87
|
type TextareaProps = Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'size'> & {
|
|
@@ -333,4 +350,38 @@ type SeparatorProps = {
|
|
|
333
350
|
};
|
|
334
351
|
declare function Separator({ className }: SeparatorProps): react_jsx_runtime.JSX.Element;
|
|
335
352
|
|
|
336
|
-
|
|
353
|
+
interface BreadcrumbItem {
|
|
354
|
+
/** Label to display */
|
|
355
|
+
label: string;
|
|
356
|
+
/** Optional href for navigation */
|
|
357
|
+
href?: string;
|
|
358
|
+
/** Optional icon name (Material Symbols) */
|
|
359
|
+
icon?: string;
|
|
360
|
+
/** Optional click handler */
|
|
361
|
+
onClick?: () => void;
|
|
362
|
+
}
|
|
363
|
+
interface BreadcrumbProps {
|
|
364
|
+
/** Array of breadcrumb items */
|
|
365
|
+
items: BreadcrumbItem[];
|
|
366
|
+
/** Whether to show home icon on first item */
|
|
367
|
+
showHomeIcon?: boolean;
|
|
368
|
+
/** Additional CSS classes */
|
|
369
|
+
className?: string;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Breadcrumb navigation component
|
|
373
|
+
*
|
|
374
|
+
* @example
|
|
375
|
+
* ```tsx
|
|
376
|
+
* <Breadcrumb
|
|
377
|
+
* items={[
|
|
378
|
+
* { label: "Home", href: "/" },
|
|
379
|
+
* { label: "Products", href: "/products" },
|
|
380
|
+
* { label: "Category" }
|
|
381
|
+
* ]}
|
|
382
|
+
* />
|
|
383
|
+
* ```
|
|
384
|
+
*/
|
|
385
|
+
declare const Breadcrumb: React__default.FC<BreadcrumbProps>;
|
|
386
|
+
|
|
387
|
+
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;
|
|
@@ -58,13 +59,29 @@ type CardProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
|
58
59
|
};
|
|
59
60
|
declare function Card({ children, className, style, showDecorations, variant, elevated, lightImage, darkImage, ...props }: CardProps): react_jsx_runtime.JSX.Element;
|
|
60
61
|
|
|
61
|
-
type InputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>,
|
|
62
|
+
type InputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> & {
|
|
62
63
|
/** Input visual variant */
|
|
63
64
|
variant?: "default" | "inline";
|
|
65
|
+
/** Optional floating label (renders a fieldset wrapper when provided) */
|
|
66
|
+
label?: string;
|
|
67
|
+
/** Additional props for the surrounding fieldset when label is set */
|
|
68
|
+
fieldsetProps?: React.FieldsetHTMLAttributes<HTMLFieldSetElement>;
|
|
69
|
+
/** Additional props for the legend when label is set */
|
|
70
|
+
legendProps?: React.HTMLAttributes<HTMLLegendElement>;
|
|
71
|
+
/** Flag to visually mark the input as errored */
|
|
72
|
+
error?: boolean;
|
|
64
73
|
};
|
|
65
74
|
declare const Input: React.ForwardRefExoticComponent<Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> & {
|
|
66
75
|
/** Input visual variant */
|
|
67
76
|
variant?: "default" | "inline";
|
|
77
|
+
/** Optional floating label (renders a fieldset wrapper when provided) */
|
|
78
|
+
label?: string;
|
|
79
|
+
/** Additional props for the surrounding fieldset when label is set */
|
|
80
|
+
fieldsetProps?: React.FieldsetHTMLAttributes<HTMLFieldSetElement>;
|
|
81
|
+
/** Additional props for the legend when label is set */
|
|
82
|
+
legendProps?: React.HTMLAttributes<HTMLLegendElement>;
|
|
83
|
+
/** Flag to visually mark the input as errored */
|
|
84
|
+
error?: boolean;
|
|
68
85
|
} & React.RefAttributes<HTMLInputElement>>;
|
|
69
86
|
|
|
70
87
|
type TextareaProps = Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'size'> & {
|
|
@@ -333,4 +350,38 @@ type SeparatorProps = {
|
|
|
333
350
|
};
|
|
334
351
|
declare function Separator({ className }: SeparatorProps): react_jsx_runtime.JSX.Element;
|
|
335
352
|
|
|
336
|
-
|
|
353
|
+
interface BreadcrumbItem {
|
|
354
|
+
/** Label to display */
|
|
355
|
+
label: string;
|
|
356
|
+
/** Optional href for navigation */
|
|
357
|
+
href?: string;
|
|
358
|
+
/** Optional icon name (Material Symbols) */
|
|
359
|
+
icon?: string;
|
|
360
|
+
/** Optional click handler */
|
|
361
|
+
onClick?: () => void;
|
|
362
|
+
}
|
|
363
|
+
interface BreadcrumbProps {
|
|
364
|
+
/** Array of breadcrumb items */
|
|
365
|
+
items: BreadcrumbItem[];
|
|
366
|
+
/** Whether to show home icon on first item */
|
|
367
|
+
showHomeIcon?: boolean;
|
|
368
|
+
/** Additional CSS classes */
|
|
369
|
+
className?: string;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Breadcrumb navigation component
|
|
373
|
+
*
|
|
374
|
+
* @example
|
|
375
|
+
* ```tsx
|
|
376
|
+
* <Breadcrumb
|
|
377
|
+
* items={[
|
|
378
|
+
* { label: "Home", href: "/" },
|
|
379
|
+
* { label: "Products", href: "/products" },
|
|
380
|
+
* { label: "Category" }
|
|
381
|
+
* ]}
|
|
382
|
+
* />
|
|
383
|
+
* ```
|
|
384
|
+
*/
|
|
385
|
+
declare const Breadcrumb: React__default.FC<BreadcrumbProps>;
|
|
386
|
+
|
|
387
|
+
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
|
@@ -244,28 +244,81 @@ function Card({
|
|
|
244
244
|
);
|
|
245
245
|
}
|
|
246
246
|
var Input = React3.forwardRef(
|
|
247
|
-
({
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
247
|
+
({
|
|
248
|
+
className,
|
|
249
|
+
disabled,
|
|
250
|
+
variant = "default",
|
|
251
|
+
label,
|
|
252
|
+
fieldsetProps,
|
|
253
|
+
legendProps,
|
|
254
|
+
error = false,
|
|
255
|
+
...props
|
|
256
|
+
}, ref) => {
|
|
257
|
+
const isInlineVariant = variant === "inline";
|
|
258
|
+
const shouldUseInlineStyles = isInlineVariant || Boolean(label);
|
|
259
|
+
const isError = error && !disabled;
|
|
260
|
+
const inputClasses = [
|
|
261
|
+
"w-full bg-transparent outline-none transition-colors",
|
|
262
|
+
shouldUseInlineStyles ? "h-9" : "h-12 px-4 rounded-full",
|
|
263
|
+
"font-['Poppins'] text-sm placeholder:text-[var(--muted-fg)]"
|
|
264
|
+
];
|
|
265
|
+
if (!shouldUseInlineStyles) {
|
|
266
|
+
inputClasses.push("border");
|
|
267
|
+
}
|
|
268
|
+
if (disabled) {
|
|
269
|
+
inputClasses.push("text-[#3F424F]", "cursor-not-allowed");
|
|
270
|
+
if (!shouldUseInlineStyles) {
|
|
271
|
+
inputClasses.push("border-[#3F424F]");
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
inputClasses.push("text-[var(--muted-fg)]", "focus:text-[var(--fg)]");
|
|
275
|
+
if (!shouldUseInlineStyles) {
|
|
276
|
+
inputClasses.push(
|
|
277
|
+
isError ? "border-[var(--destructive)]" : "border-[var(--muted-fg)]",
|
|
278
|
+
isError ? "hover:border-[var(--destructive)]" : "hover:border-[var(--border)]",
|
|
279
|
+
isError ? "focus:border-[var(--destructive)]" : "focus:border-[var(--color-brand)]"
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (className) {
|
|
284
|
+
inputClasses.push(className);
|
|
285
|
+
}
|
|
286
|
+
const inputClassName = inputClasses.join(" ");
|
|
287
|
+
const inputElement = /* @__PURE__ */ jsx("input", { ref, disabled, className: inputClassName, ...props });
|
|
288
|
+
if (!label) {
|
|
289
|
+
return inputElement;
|
|
290
|
+
}
|
|
291
|
+
const { className: fieldsetClassNameProp = "", ...restFieldsetProps } = fieldsetProps ?? {};
|
|
292
|
+
const { className: legendClassNameProp = "", ...restLegendProps } = legendProps ?? {};
|
|
293
|
+
const fieldsetClassName = [
|
|
294
|
+
"w-full min-w-0 rounded-full border bg-[var(--surface)] transition-colors h-14",
|
|
295
|
+
isError ? "border-[var(--destructive)]" : "border-[var(--border)]",
|
|
296
|
+
isError ? "focus-within:border-[var(--destructive)]" : "focus-within:border-[var(--color-brand)]",
|
|
297
|
+
disabled ? "opacity-60 cursor-not-allowed" : "",
|
|
298
|
+
fieldsetClassNameProp
|
|
299
|
+
].filter(Boolean).join(" ");
|
|
300
|
+
const legendColorClass = disabled ? "text-[var(--muted-fg)]" : isError ? "text-[var(--destructive)]" : "text-[var(--muted-fg)]";
|
|
301
|
+
const legendClassNameCombined = [
|
|
302
|
+
"ml-4 px-1 text-sm leading-none relative font-normal select-none",
|
|
303
|
+
legendColorClass,
|
|
304
|
+
legendClassNameProp
|
|
305
|
+
].filter(Boolean).join(" ");
|
|
306
|
+
return /* @__PURE__ */ jsxs(
|
|
307
|
+
"fieldset",
|
|
251
308
|
{
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
].join(" "),
|
|
266
|
-
className
|
|
267
|
-
].join(" "),
|
|
268
|
-
...props
|
|
309
|
+
...restFieldsetProps,
|
|
310
|
+
className: fieldsetClassName,
|
|
311
|
+
children: [
|
|
312
|
+
/* @__PURE__ */ jsx(
|
|
313
|
+
"legend",
|
|
314
|
+
{
|
|
315
|
+
...restLegendProps,
|
|
316
|
+
className: legendClassNameCombined,
|
|
317
|
+
children: label
|
|
318
|
+
}
|
|
319
|
+
),
|
|
320
|
+
/* @__PURE__ */ jsx("div", { className: "relative flex pl-5 pr-3 pb-1 h-full", children: /* @__PURE__ */ jsx("div", { className: "flex w-full", children: inputElement }) })
|
|
321
|
+
]
|
|
269
322
|
}
|
|
270
323
|
);
|
|
271
324
|
}
|
|
@@ -1370,5 +1423,59 @@ MessageBubble.displayName = "MessageBubble";
|
|
|
1370
1423
|
function Separator({ className = "" }) {
|
|
1371
1424
|
return /* @__PURE__ */ jsx("div", { className: `w-full my-1.5 h-px bg-[var(--border)] ${className}` });
|
|
1372
1425
|
}
|
|
1426
|
+
var Breadcrumb = ({
|
|
1427
|
+
items,
|
|
1428
|
+
showHomeIcon = false,
|
|
1429
|
+
className = ""
|
|
1430
|
+
}) => {
|
|
1431
|
+
if (!items || items.length === 0) {
|
|
1432
|
+
return null;
|
|
1433
|
+
}
|
|
1434
|
+
return /* @__PURE__ */ jsx("nav", { "aria-label": "Breadcrumb", className, children: /* @__PURE__ */ jsx("ol", { className: "flex items-center flex-wrap", children: items.map((item, index) => {
|
|
1435
|
+
const isLast = index === items.length - 1;
|
|
1436
|
+
const isFirst = index === 0;
|
|
1437
|
+
return /* @__PURE__ */ jsxs("li", { className: "flex items-center", children: [
|
|
1438
|
+
item.href && !isLast ? /* @__PURE__ */ jsxs(
|
|
1439
|
+
"a",
|
|
1440
|
+
{
|
|
1441
|
+
href: item.href,
|
|
1442
|
+
onClick: (e) => {
|
|
1443
|
+
if (item.onClick) {
|
|
1444
|
+
e.preventDefault();
|
|
1445
|
+
item.onClick();
|
|
1446
|
+
}
|
|
1447
|
+
},
|
|
1448
|
+
className: "group flex items-center gap-1 cursor-pointer text-[var(--muted-fg)]",
|
|
1449
|
+
children: [
|
|
1450
|
+
isFirst && showHomeIcon && /* @__PURE__ */ jsx(Icon, { name: "home", size: "sm" }),
|
|
1451
|
+
item.icon && !showHomeIcon && /* @__PURE__ */ jsx(Icon, { name: item.icon, size: "sm" }),
|
|
1452
|
+
/* @__PURE__ */ jsx(Typo, { variant: "label-md", bold: "semibold", className: "group-hover:underline", children: item.label })
|
|
1453
|
+
]
|
|
1454
|
+
}
|
|
1455
|
+
) : /* @__PURE__ */ jsxs(
|
|
1456
|
+
"span",
|
|
1457
|
+
{
|
|
1458
|
+
className: `group flex items-center gap-1 ${isLast ? "text-[var(--info)]" : "text-[var(--muted-fg)]"} ${item.onClick && !isLast ? "cursor-pointer" : ""}`,
|
|
1459
|
+
onClick: item.onClick,
|
|
1460
|
+
role: item.onClick && !isLast ? "button" : void 0,
|
|
1461
|
+
tabIndex: item.onClick && !isLast ? 0 : void 0,
|
|
1462
|
+
onKeyDown: item.onClick && !isLast ? (e) => {
|
|
1463
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1464
|
+
e.preventDefault();
|
|
1465
|
+
item.onClick?.();
|
|
1466
|
+
}
|
|
1467
|
+
} : void 0,
|
|
1468
|
+
"aria-current": isLast ? "page" : void 0,
|
|
1469
|
+
children: [
|
|
1470
|
+
isFirst && showHomeIcon && /* @__PURE__ */ jsx(Icon, { name: "home", size: "sm" }),
|
|
1471
|
+
item.icon && !showHomeIcon && /* @__PURE__ */ jsx(Icon, { name: item.icon, size: "sm" }),
|
|
1472
|
+
/* @__PURE__ */ jsx(Typo, { variant: "label-md", bold: isLast ? "bold" : "semibold", className: item.onClick && !isLast ? "group-hover:underline" : "", children: item.label })
|
|
1473
|
+
]
|
|
1474
|
+
}
|
|
1475
|
+
),
|
|
1476
|
+
!isLast && /* @__PURE__ */ jsx("span", { className: "text-[var(--muted-fg)]", children: /* @__PURE__ */ jsx(Icon, { name: "chevron_right", size: "md" }) })
|
|
1477
|
+
] }, index);
|
|
1478
|
+
}) }) });
|
|
1479
|
+
};
|
|
1373
1480
|
|
|
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 };
|
|
1481
|
+
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neoptocom/neopto-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
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": [
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"typecheck": "tsc --noEmit",
|
|
50
50
|
"storybook": "storybook dev -p 6006",
|
|
51
51
|
"build-storybook": "storybook build",
|
|
52
|
+
"storybook:docs": "storybook build --docs --output-dir storybook-static && cp storybook-static/index.json storybook-static/docs.json",
|
|
52
53
|
"prepublishOnly": "npm run build && npm run typecheck",
|
|
53
54
|
"changeset": "changeset",
|
|
54
55
|
"version-packages": "changeset version",
|
|
@@ -58,9 +59,9 @@
|
|
|
58
59
|
"react": ">=18",
|
|
59
60
|
"react-dom": ">=18"
|
|
60
61
|
},
|
|
61
|
-
"dependencies": {},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@changesets/cli": "^2.27.8",
|
|
64
|
+
"@joshwooding/vite-plugin-react-docgen-typescript": "^0.6.2",
|
|
64
65
|
"@storybook/addon-actions": "^8.1.0",
|
|
65
66
|
"@storybook/addon-essentials": "^8.1.0",
|
|
66
67
|
"@storybook/addon-interactions": "^8.1.0",
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Meta, Canvas, Story, ArgsTable } from "@storybook/blocks";
|
|
2
|
+
import { Breadcrumb } from "./Breadcrumb";
|
|
3
|
+
import * as BreadcrumbStories from "./Breadcrumb.stories";
|
|
4
|
+
|
|
5
|
+
<Meta of={BreadcrumbStories} />
|
|
6
|
+
|
|
7
|
+
# Breadcrumb
|
|
8
|
+
|
|
9
|
+
The `Breadcrumb` component displays the current location within a hierarchy and optional navigation
|
|
10
|
+
links for parent levels. It supports icons, external links, and click handlers for in-app routing.
|
|
11
|
+
|
|
12
|
+
## When to use
|
|
13
|
+
|
|
14
|
+
- Help users understand where they are in a nested content structure.
|
|
15
|
+
- Offer a quick path back to previous sections without duplicating navigation in headers or sidebars.
|
|
16
|
+
- Pair with routed or client-side navigation; items accept either `href` or `onClick`.
|
|
17
|
+
|
|
18
|
+
## Basic usage
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
import { Breadcrumb } from "@neoptocom/neopto-ui";
|
|
22
|
+
|
|
23
|
+
const items = [
|
|
24
|
+
{ label: "Home", href: "/" },
|
|
25
|
+
{ label: "Docs", href: "/docs" },
|
|
26
|
+
{ label: "Components" }
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
<Breadcrumb items={items} showHomeIcon />;
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
<Canvas>
|
|
33
|
+
<Story of={BreadcrumbStories.Playground} />
|
|
34
|
+
</Canvas>
|
|
35
|
+
|
|
36
|
+
<ArgsTable of={BreadcrumbStories.Playground} />
|
|
37
|
+
|
|
38
|
+
## Design guidance
|
|
39
|
+
|
|
40
|
+
- Keep labels short; truncate or abbreviate long section names where possible.
|
|
41
|
+
- Provide icons sparingly to reinforce key contexts (e.g., folders vs. people).
|
|
42
|
+
- Use `showHomeIcon` to reinforce a “home” entry without duplicating the label.
|
|
43
|
+
- Avoid mixing `href` and `onClick` handlers on the same item.
|
|
44
|
+
|
|
45
|
+
## Composing with state
|
|
46
|
+
|
|
47
|
+
Combine breadcrumb click handlers with your router or state machine to switch views without a full
|
|
48
|
+
page load. The interactive example demonstrates an in-app documents flow.
|
|
49
|
+
|
|
50
|
+
<Canvas>
|
|
51
|
+
<Story of={BreadcrumbStories.InteractiveNavigation} />
|
|
52
|
+
</Canvas>
|
|
53
|
+
|
|
54
|
+
## Accessibility
|
|
55
|
+
|
|
56
|
+
- Breadcrumbs are wrapped in a `<nav aria-label="Breadcrumb">` for screen readers.
|
|
57
|
+
- The final item receives `aria-current="page"` automatically.
|
|
58
|
+
- Clickable items expose keyboard handlers, so always keep labels descriptive.
|
|
59
|
+
|
|
60
|
+
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Breadcrumb, type BreadcrumbItem } from "./Breadcrumb";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Breadcrumb> = {
|
|
6
|
+
title: "Components/Breadcrumb",
|
|
7
|
+
component: Breadcrumb,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
parameters: {
|
|
10
|
+
layout: "centered"
|
|
11
|
+
},
|
|
12
|
+
args: {
|
|
13
|
+
items: [
|
|
14
|
+
{ label: "Home", href: "/" },
|
|
15
|
+
{ label: "Library", href: "/library" },
|
|
16
|
+
{ label: "Data" }
|
|
17
|
+
] satisfies BreadcrumbItem[]
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default meta;
|
|
22
|
+
type Story = StoryObj<typeof Breadcrumb>;
|
|
23
|
+
|
|
24
|
+
export const Playground: Story = {};
|
|
25
|
+
|
|
26
|
+
export const WithIcons: Story = {
|
|
27
|
+
args: {
|
|
28
|
+
items: [
|
|
29
|
+
{ label: "Dashboard", href: "/", icon: "dashboard" },
|
|
30
|
+
{ label: "Projects", href: "/projects", icon: "folder" },
|
|
31
|
+
{ label: "Neptune Launch", icon: "rocket_launch" }
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const WithHomeIcon: Story = {
|
|
37
|
+
args: {
|
|
38
|
+
showHomeIcon: true
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const InteractiveNavigation: Story = {
|
|
43
|
+
render: (args) => {
|
|
44
|
+
const sections: BreadcrumbItem[] = [
|
|
45
|
+
{ label: "Getting Started", href: "/docs/getting-started" },
|
|
46
|
+
{ label: "Guides", href: "/docs/guides" },
|
|
47
|
+
{ label: "Auth", icon: "lock" }
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const [activeSection, setActiveSection] = useState(sections[sections.length - 1]);
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div className="flex flex-col gap-6 w-full max-w-3xl">
|
|
54
|
+
<Breadcrumb
|
|
55
|
+
{...args}
|
|
56
|
+
items={[
|
|
57
|
+
{ label: "Docs", href: "/docs", icon: "menu_book" },
|
|
58
|
+
...sections.map((section, index) => ({
|
|
59
|
+
...section,
|
|
60
|
+
onClick: () => setActiveSection(section),
|
|
61
|
+
href: index === sections.length - 1 ? undefined : section.href
|
|
62
|
+
}))
|
|
63
|
+
]}
|
|
64
|
+
/>
|
|
65
|
+
|
|
66
|
+
<section className="rounded-lg border border-[var(--border)] bg-[var(--surface)] p-6 space-y-2">
|
|
67
|
+
<h2 className="text-xl font-semibold">{activeSection.label}</h2>
|
|
68
|
+
<p className="text-sm text-[var(--muted-fg)]">
|
|
69
|
+
Showcase how breadcrumbs can drive in-app navigation without reloading the page by
|
|
70
|
+
combining click handlers with your own state management.
|
|
71
|
+
</p>
|
|
72
|
+
</section>
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
|